agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v3 1/7] Row pattern recognition patch for raw parser.
220+ messages / 2 participants
[nested] [flat]

* [PATCH v3 1/7] Row pattern recognition patch for raw parser.
@ 2023-07-26 10:49 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Tatsuo Ishii @ 2023-07-26 10:49 UTC (permalink / raw)

---
 src/backend/parser/gram.y       | 218 +++++++++++++++++++++++++++++---
 src/include/nodes/parsenodes.h  |  54 ++++++++
 src/include/parser/kwlist.h     |   8 ++
 src/include/parser/parse_node.h |   1 +
 4 files changed, 266 insertions(+), 15 deletions(-)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 856d5dee0e..a4c97c28ff 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -251,6 +251,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	DefElem	   *defelt;
 	SortBy	   *sortby;
 	WindowDef  *windef;
+	RPCommonSyntax	*rpcom;
+	RPSubsetItem	*rpsubset;
 	JoinExpr   *jexpr;
 	IndexElem  *ielem;
 	StatsElem  *selem;
@@ -453,8 +455,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				TriggerTransitions TriggerReferencing
 				vacuum_relation_list opt_vacuum_relation_list
 				drop_option_list pub_obj_list
-
-%type <node>	opt_routine_body
+				row_pattern_measure_list row_pattern_definition_list
+				opt_row_pattern_subset_clause
+				row_pattern_subset_list row_pattern_subset_rhs
+				row_pattern
+%type <rpsubset>	 row_pattern_subset_item
+%type <node>	opt_routine_body row_pattern_term
 %type <groupclause> group_clause
 %type <list>	group_by_list
 %type <node>	group_by_item empty_grouping_set rollup_clause cube_clause
@@ -551,6 +557,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
+				row_pattern_measure_item row_pattern_definition
 
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
@@ -633,6 +640,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>	window_clause window_definition_list opt_partition_clause
 %type <windef>	window_definition over_clause window_specification
 				opt_frame_clause frame_extent frame_bound
+%type <rpcom>	opt_row_pattern_common_syntax opt_row_pattern_skip_to
+%type <boolean>	opt_row_pattern_initial_or_seek
+%type <list>	opt_row_pattern_measures
 %type <ival>	opt_window_exclusion_clause
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
@@ -659,7 +669,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				json_object_constructor_null_clause_opt
 				json_array_constructor_null_clause_opt
 
-
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -702,7 +711,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
 
@@ -718,7 +727,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
 
@@ -731,7 +740,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 METHOD
+	MAPPING MATCH MATCHED MATERIALIZED MAXVALUE MEASURES MERGE METHOD
 	MINUTE_P MINVALUE MODE MONTH_P MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEW NEXT NFC NFD NFKC NFKD NO NONE
@@ -743,9 +752,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
-	PLACING PLANS POLICY
-	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PAST
+	PATTERN PLACING PLANS POLICY
+	POSITION PRECEDING PRECISION PREMUTE PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
 	QUOTE
@@ -755,12 +764,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 SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_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 TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -853,6 +863,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  */
 %nonassoc	UNBOUNDED		/* ideally would have same precedence as IDENT */
 %nonassoc	IDENT PARTITION RANGE ROWS GROUPS PRECEDING FOLLOWING CUBE ROLLUP
+%nonassoc	MEASURES AFTER INITIAL SEEK PATTERN
 %left		Op OPERATOR		/* multi-character ops and user-defined operators */
 %left		'+' '-'
 %left		'*' '/' '%'
@@ -15818,7 +15829,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);
 
@@ -15826,10 +15838,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 = $7;
 					n->location = @1;
 					$$ = n;
 				}
@@ -15853,6 +15867,31 @@ opt_partition_clause: PARTITION BY expr_list		{ $$ = $3; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
+/*
+ * ROW PATTERN 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.
@@ -16012,6 +16051,139 @@ opt_window_exclusion_clause:
 			| /*EMPTY*/				{ $$ = 0; }
 		;
 
+opt_row_pattern_common_syntax:
+opt_row_pattern_skip_to opt_row_pattern_initial_or_seek
+				PATTERN '(' row_pattern ')'
+				opt_row_pattern_subset_clause
+				DEFINE row_pattern_definition_list
+			{
+				RPCommonSyntax *n = makeNode(RPCommonSyntax);
+				n->rpSkipTo = $1->rpSkipTo;
+				n->rpSkipVariable = $1->rpSkipVariable;
+				n->initial = $2;
+				n->rpPatterns = $5;
+				n->rpSubsetClause = $7;
+				n->rpDefs = $9;
+				$$ = 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;
+					$$ = n;
+			}
+			| AFTER MATCH SKIP PAST LAST_P ROW
+				{
+					RPCommonSyntax *n = makeNode(RPCommonSyntax);
+					n->rpSkipTo = ST_PAST_LAST_ROW;
+					n->rpSkipVariable = NULL;
+					$$ = n;
+				}
+/*
+			| AFTER MATCH SKIP TO FIRST_P ColId		%prec FIRST_P
+				{
+					RPCommonSyntax *n = makeNode(RPCommonSyntax);
+					n->rpSkipTo = ST_FIRST_VARIABLE;
+					n->rpSkipVariable = $6;
+					$$ = n;
+				}
+			| AFTER MATCH SKIP TO LAST_P ColId		%prec LAST_P
+				{
+					RPCommonSyntax *n = makeNode(RPCommonSyntax);
+					n->rpSkipTo = ST_LAST_VARIABLE;
+					n->rpSkipVariable = $6;
+					$$ = n;
+				}
+ * Shift/reduce
+			| 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;
+					$$ = n;
+				}
+	;
+
+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;
+				$$ = 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.
@@ -17107,6 +17279,7 @@ unreserved_keyword:
 			| INDEXES
 			| INHERIT
 			| INHERITS
+			| INITIAL
 			| INLINE_P
 			| INPUT_P
 			| INSENSITIVE
@@ -17134,6 +17307,7 @@ unreserved_keyword:
 			| MATCHED
 			| MATERIALIZED
 			| MAXVALUE
+			| MEASURES
 			| MERGE
 			| METHOD
 			| MINUTE_P
@@ -17176,9 +17350,12 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PAST
+			| PATTERN
 			| PLANS
 			| POLICY
 			| PRECEDING
+			| PREMUTE
 			| PREPARE
 			| PREPARED
 			| PRESERVE
@@ -17226,6 +17403,7 @@ unreserved_keyword:
 			| SEARCH
 			| SECOND_P
 			| SECURITY
+			| SEEK
 			| SEQUENCE
 			| SEQUENCES
 			| SERIALIZABLE
@@ -17251,6 +17429,7 @@ unreserved_keyword:
 			| STRICT_P
 			| STRIP_P
 			| SUBSCRIPTION
+			| SUBSET
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
@@ -17438,6 +17617,7 @@ reserved_keyword:
 			| CURRENT_USER
 			| DEFAULT
 			| DEFERRABLE
+			| DEFINE
 			| DESC
 			| DISTINCT
 			| DO
@@ -17600,6 +17780,7 @@ bare_label_keyword:
 			| DEFAULTS
 			| DEFERRABLE
 			| DEFERRED
+			| DEFINE
 			| DEFINER
 			| DELETE_P
 			| DELIMITER
@@ -17675,6 +17856,7 @@ bare_label_keyword:
 			| INDEXES
 			| INHERIT
 			| INHERITS
+			| INITIAL
 			| INITIALLY
 			| INLINE_P
 			| INNER_P
@@ -17724,6 +17906,7 @@ bare_label_keyword:
 			| MATCHED
 			| MATERIALIZED
 			| MAXVALUE
+			| MEASURES
 			| MERGE
 			| METHOD
 			| MINVALUE
@@ -17777,11 +17960,14 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PAST
+			| PATTERN
 			| PLACING
 			| PLANS
 			| POLICY
 			| POSITION
 			| PRECEDING
+			| PREMUTE
 			| PREPARE
 			| PREPARED
 			| PRESERVE
@@ -17833,6 +18019,7 @@ bare_label_keyword:
 			| SCROLL
 			| SEARCH
 			| SECURITY
+			| SEEK
 			| SELECT
 			| SEQUENCE
 			| SEQUENCES
@@ -17864,6 +18051,7 @@ bare_label_keyword:
 			| STRICT_P
 			| STRIP_P
 			| SUBSCRIPTION
+			| SUBSET
 			| SUBSTRING
 			| SUPPORT
 			| SYMMETRIC
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 228cdca0f1..c56c0d71c2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -547,6 +547,44 @@ typedef struct SortBy
 	int			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
  *
@@ -562,6 +600,8 @@ 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 */
@@ -1483,6 +1523,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.
  */
@@ -1514,6 +1559,15 @@ 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 Recognition AFTER MACH SKIP clause */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	bool		initial;		/* true if <row pattern initial or seek> is initial */
+	/* Row Pattern Recognition DEFINE clause (list of TargetEntry) */
+	List		*defineClause;
+	/* Row Pattern Recognition PATTERN variable name (list of String) */
+	List		*patternVariable;
+	/* Row Pattern Recognition 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 5984dcfa4b..c799770025 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -128,6 +128,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)
@@ -212,6 +213,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)
@@ -265,6 +267,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("method", METHOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD, AS_LABEL)
@@ -326,12 +329,15 @@ 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("pattern", PATTERN, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD, AS_LABEL)
+PG_KEYWORD("premute", PREMUTE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("prepare", PREPARE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("prepared", PREPARED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("preserve", PRESERVE, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -385,6 +391,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)
@@ -416,6 +423,7 @@ PG_KEYWORD("stored", STORED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("strict", STRICT_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 f589112d5e..6640090910 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(Wed_Jul_26_21_21_34_2023_317)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v3-0002-Row-pattern-recognition-patch-parse-analysis.patch"



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-06-16 11:54 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-06-16 11:54 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..fa5a53d04ff 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..2af586669ae 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -689,6 +689,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1376,22 +1378,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1408,9 +1404,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1495,9 +1489,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1524,7 +1516,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1537,8 +1529,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -3926,27 +3918,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4034,11 +4018,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2debadd86ed..28b34fd4387 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -503,13 +503,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -528,15 +526,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -546,7 +538,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -670,11 +662,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1207,7 +1196,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 38f9ffcd04f..c0f6217caa6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23245,9 +23245,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23379,18 +23377,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23427,11 +23416,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..8922d713916 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4d5abbaca7..90234c3f736 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1325,6 +1325,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v01-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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

* [PATCH 5/8] Simplify the way restrictions are imposed on index functions.
@ 2026-07-15 08:37 Antonin Houska <[email protected]>
  0 siblings, 0 replies; 220+ messages in thread

From: Antonin Houska @ 2026-07-15 08:37 UTC (permalink / raw)

Whenever we expect possible execution of index functions, we need to make sure
that they execute with the appropriate privileges. Also, the core should not
see (and use) values of GUC parameters introduced by the index functions.

This patch introduces functions enable_index_build_security() and
disable_index_build_security() which make the security measures less
verbose. It's needed for the upcoming enhancements of REPACK (CONCURRENTLY),
but looks like useful refactoring anyway.
---
 src/backend/access/brin/brin.c   | 32 ++++----------
 src/backend/catalog/index.c      | 72 ++++++++++++++++++--------------
 src/backend/commands/analyze.c   | 21 +++-------
 src/backend/commands/indexcmds.c | 49 +++++++---------------
 src/backend/commands/repack.c    | 24 +++--------
 src/backend/commands/tablecmds.c | 24 +++--------
 src/backend/commands/vacuum.c    | 23 +++-------
 src/include/catalog/index.h      | 10 +++++
 src/tools/pgindent/typedefs.list |  1 +
 9 files changed, 95 insertions(+), 161 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..2359bffa518 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -1391,10 +1391,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 	Oid			heapoid;
 	Relation	indexRel;
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	double		numSummarized = 0;
+	IndexBuildSecurity ibsec;
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -1420,27 +1418,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 		heapRel = table_open(heapoid, ShareUpdateExclusiveLock);
 
 		/*
-		 * Autovacuum calls us.  For its benefit, switch to the table owner's
-		 * userid, so that any index functions are run as that user.  Also
-		 * lock down security-restricted operations and arrange to make GUC
-		 * variable changes local to this command.  This is harmless, albeit
-		 * unnecessary, when called from SQL, because we fail shortly if the
-		 * user does not own the index.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 	}
 	else
-	{
 		heapRel = NULL;
-		/* Set these just to suppress "uninitialized variable" warnings */
-		save_userid = InvalidOid;
-		save_sec_context = -1;
-		save_nestlevel = -1;
-	}
 
 	indexRel = index_open(indexoid, ShareUpdateExclusiveLock);
 
@@ -1453,7 +1436,8 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 						RelationGetRelationName(indexRel))));
 
 	/* User must own the index (comparable to privileges needed for VACUUM) */
-	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid, save_userid))
+	if (heapRel != NULL && !object_ownercheck(RelationRelationId, indexoid,
+											  ibsec.userid))
 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
 					   RelationGetRelationName(indexRel));
 
@@ -1477,11 +1461,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
 				 errmsg("index \"%s\" is not valid",
 						RelationGetRelationName(indexRel))));
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
 
 	index_close(indexRel, ShareUpdateExclusiveLock);
 	table_close(heapRel, ShareUpdateExclusiveLock);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 81bba4beac7..8eabe232d1e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1504,11 +1504,9 @@ index_concurrently_build(Oid heapRelationId,
 						 Oid indexRelationId)
 {
 	Relation	heapRel;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	Relation	indexRelation;
 	IndexInfo  *indexInfo;
+	IndexBuildSecurity ibsec;
 
 	/* This had better make sure that a snapshot is active */
 	Assert(ActiveSnapshotSet());
@@ -1517,15 +1515,9 @@ index_concurrently_build(Oid heapRelationId,
 	heapRel = table_open(heapRelationId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexRelationId, RowExclusiveLock);
 
@@ -1542,11 +1534,8 @@ index_concurrently_build(Oid heapRelationId,
 	/* Now build the index */
 	index_build(heapRel, indexRelation, indexInfo, false, true, true);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close both the relations, but keep the locks */
 	table_close(heapRel, NoLock);
@@ -3375,9 +3364,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	IndexInfo  *indexInfo;
 	IndexVacuumInfo ivinfo;
 	ValidateIndexState state;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	{
 		const int	progress_index[] = {
@@ -3399,15 +3386,9 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(heapRelation->rd_rel->relowner, &ibsec);
 
 	indexRelation = index_open(indexId, RowExclusiveLock);
 
@@ -3486,11 +3467,8 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 		 "validate_index found %.0f heap tuples, %.0f index tuples; inserted %.0f missing tuples",
 		 state.htups, state.itups, state.tups_inserted);
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Close rels, but keep locks */
 	index_close(indexRelation, NoLock);
@@ -4115,6 +4093,36 @@ reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 	return result;
 }
 
+/*
+ * Before building an index, witch to the table owner's userid, so that any
+ * index functions are run as that user. Also lock down security-restricted
+ * operations and arrange to make GUC variable changes local to this command.
+ *
+ * Information needed later by disable_index_build_security() is stored in
+ * *sec.
+ */
+void
+enable_index_build_security(Oid userid, IndexBuildSecurity *sec)
+{
+	GetUserIdAndSecContext(&sec->userid, &sec->sec_context);
+	SetUserIdAndSecContext(userid,
+						   sec->sec_context | SECURITY_RESTRICTED_OPERATION);
+	sec->nestlevel = NewGUCNestLevel();
+	RestrictSearchPath();
+}
+
+/*
+ * Undo what enable_index_build_security() did.
+ */
+void
+disable_index_build_security(IndexBuildSecurity *sec)
+{
+	/* Roll back any GUC changes executed by index functions */
+	AtEOXact_GUC(false, sec->nestlevel);
+
+	/* Restore userid and security context */
+	SetUserIdAndSecContext(sec->userid, sec->sec_context);
+}
 
 /* ----------------------------------------------------------------
  *		System index reindexing support
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..dc2ad77ef9a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -328,14 +328,12 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	PGRUsage	ru0;
 	TimestampTz starttime = 0;
 	MemoryContext caller_context;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	WalUsage	startwalusage = pgWalUsage;
 	BufferUsage startbufferusage = pgBufferUsage;
 	BufferUsage bufferusage;
 	PgStat_Counter startreadtime = 0;
 	PgStat_Counter startwritetime = 0;
+	IndexBuildSecurity ibsec;
 
 	verbose = (params->options & VACOPT_VERBOSE) != 0;
 	instrument = (verbose || (AmAutoVacuumWorkerProcess() &&
@@ -361,15 +359,9 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 	caller_context = MemoryContextSwitchTo(anl_context);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(onerel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(onerel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * When verbose or autovacuum logging is used, initialize a resource usage
@@ -858,11 +850,8 @@ do_analyze_rel(Relation onerel, const VacuumParams *params,
 		}
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* Restore current context and release memory */
 	MemoryContextSwitchTo(caller_context);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..5bc28c94139 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -699,6 +699,8 @@ DefineIndex(ParseState *pstate,
 	 * Switch to the table owner's userid, so that any index functions are run
 	 * as that user.  Also lock down security-restricted operations.  We
 	 * already arranged to make GUC variable changes local to this command.
+	 *
+	 * XXX Use enable_index_build_security()?
 	 */
 	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
 	SetUserIdAndSecContext(rel->rd_rel->relowner,
@@ -1386,22 +1388,16 @@ DefineIndex(ParseState *pstate,
 			{
 				Oid			childRelid = part_oids[i];
 				Relation	childrel;
-				Oid			child_save_userid;
-				int			child_save_sec_context;
-				int			child_save_nestlevel;
 				List	   *childidxs;
 				ListCell   *cell;
 				AttrMap    *attmap;
 				bool		found = false;
+				IndexBuildSecurity child_ibsec;
 
 				childrel = table_open(childRelid, lockmode);
 
-				GetUserIdAndSecContext(&child_save_userid,
-									   &child_save_sec_context);
-				SetUserIdAndSecContext(childrel->rd_rel->relowner,
-									   child_save_sec_context | SECURITY_RESTRICTED_OPERATION);
-				child_save_nestlevel = NewGUCNestLevel();
-				RestrictSearchPath();
+				enable_index_build_security(childrel->rd_rel->relowner,
+											&child_ibsec);
 
 				/*
 				 * Don't try to create indexes on foreign tables, though. Skip
@@ -1418,9 +1414,7 @@ DefineIndex(ParseState *pstate,
 								 errdetail("Table \"%s\" contains partitions that are foreign tables.",
 										   RelationGetRelationName(rel))));
 
-					AtEOXact_GUC(false, child_save_nestlevel);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					disable_index_build_security(&child_ibsec);
 					table_close(childrel, lockmode);
 					continue;
 				}
@@ -1505,9 +1499,7 @@ DefineIndex(ParseState *pstate,
 				}
 
 				list_free(childidxs);
-				AtEOXact_GUC(false, child_save_nestlevel);
-				SetUserIdAndSecContext(child_save_userid,
-									   child_save_sec_context);
+				disable_index_build_security(&child_ibsec);
 				table_close(childrel, NoLock);
 
 				/*
@@ -1534,7 +1526,7 @@ DefineIndex(ParseState *pstate,
 					 * Recurse as the starting user ID.  Callee will use that
 					 * for permission checks, then switch again.
 					 */
-					Assert(GetUserId() == child_save_userid);
+					Assert(GetUserId() == child_ibsec.userid);
 					SetUserIdAndSecContext(root_save_userid,
 										   root_save_sec_context);
 					childAddr =
@@ -1547,8 +1539,8 @@ DefineIndex(ParseState *pstate,
 									is_alter_table, check_rights,
 									check_not_in_use,
 									skip_build, quiet);
-					SetUserIdAndSecContext(child_save_userid,
-										   child_save_sec_context);
+					SetUserIdAndSecContext(child_ibsec.userid,
+										   child_ibsec.sec_context);
 
 					/*
 					 * Check if the index just created is valid or not, as it
@@ -4051,27 +4043,19 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			newIndexId;
 		Relation	indexRel;
 		Relation	heapRel;
-		Oid			save_userid;
-		int			save_sec_context;
-		int			save_nestlevel;
 		Relation	newIndexRel;
 		LockRelId  *lockrelid;
 		Oid			tablespaceid;
+		IndexBuildSecurity ibsec;
 
 		indexRel = index_open(idx->indexId, ShareUpdateExclusiveLock);
 		heapRel = table_open(indexRel->rd_index->indrelid,
 							 ShareUpdateExclusiveLock);
 
 		/*
-		 * Switch to the table owner's userid, so that any index functions are
-		 * run as that user.  Also lock down security-restricted operations
-		 * and arrange to make GUC variable changes local to this command.
+		 * Prevent index functions from doing what they are not supposed to.
 		 */
-		GetUserIdAndSecContext(&save_userid, &save_sec_context);
-		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
-							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-		save_nestlevel = NewGUCNestLevel();
-		RestrictSearchPath();
+		enable_index_build_security(heapRel->rd_rel->relowner, &ibsec);
 
 		/* determine safety of this index for set_indexsafe_procflags */
 		idx->safe = (RelationGetIndexExpressions(indexRel) == NIL &&
@@ -4159,11 +4143,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		index_close(indexRel, NoLock);
 		index_close(newIndexRel, NoLock);
 
-		/* Roll back any GUC changes executed by index functions */
-		AtEOXact_GUC(false, save_nestlevel);
-
-		/* Restore userid and security context */
-		SetUserIdAndSecContext(save_userid, save_sec_context);
+		/* Relax the restrictions imposed above. */
+		disable_index_build_security(&ibsec);
 
 		table_close(heapRel, NoLock);
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0bf19d07db5..1ad453a39a0 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -514,13 +514,11 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	Oid			tableOid = RelationGetRelid(OldHeap);
 	Relation	index;
 	LOCKMODE	lmode;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	bool		verbose = ((params->options & CLUOPT_VERBOSE) != 0);
 	bool		recheck = ((params->options & CLUOPT_RECHECK) != 0);
 	bool		concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
 	Oid			ident_idx = InvalidOid;
+	IndexBuildSecurity ibsec;
 
 	/* Determine the lock mode to use. */
 	lmode = RepackLockLevel(concurrent);
@@ -539,15 +537,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(OldHeap->rd_rel->relowner, &ibsec);
 
 	/*
 	 * Recheck that the relation is still what it was when we started.
@@ -557,7 +549,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * not-previously-clustered index.
 	 */
 	if (recheck &&
-		!cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
+		!cluster_rel_recheck(cmd, OldHeap, indexOid, GetUserId(),
 							 lmode, params->options))
 		goto out;
 
@@ -681,11 +673,8 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 		rebuild_relation(OldHeap, index, verbose, ident_idx);
 
 out:
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	pgstat_progress_end_command();
 }
@@ -1234,7 +1223,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose,
 	}
 }
 
-
 /*
  * Create the transient table that will be filled with new data during
  * CLUSTER, ALTER TABLE, and similar operations.  The transient table
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb93c3e935a..d691b317011 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -23762,9 +23762,7 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			defaultPartOid;
 	Oid			existingRelid;
 	Oid			ownerId = InvalidOid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * Check ownership of merged partitions - partitions with different owners
@@ -23896,18 +23894,9 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	newPartRel = createPartitionTable(wqueue, cmd->name, rel, ownerId);
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also, lockdown security-restricted operations and
-	 * arrange to make GUC variable changes local to this command.
-	 *
-	 * Need to do it after determining the namespace in the
-	 * createPartitionTable() call.
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(ownerId,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(ownerId, &ibsec);
 
 	/* Copy data from merged partitions to the new partition. */
 	MergePartitionsMoveRows(wqueue, mergingPartitions, newPartRel);
@@ -23944,11 +23933,8 @@ ATExecMergePartitions(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Keep the lock until commit. */
 	table_close(newPartRel, NoLock);
 
-	/* Roll back any GUC changes executed by index functions. */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore the userid and security context. */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 }
 
 /*
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..5d1cbc382fa 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -34,6 +34,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -2017,10 +2018,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 	LockRelId	lockrelid;
 	Oid			priv_relid;
 	Oid			toast_relid;
-	Oid			save_userid;
-	int			save_sec_context;
-	int			save_nestlevel;
 	VacuumParams toast_vacuum_params;
+	IndexBuildSecurity ibsec;
 
 	/*
 	 * This function scribbles on the parameters, so make a copy early to
@@ -2270,16 +2269,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 		toast_relid = InvalidOid;
 
 	/*
-	 * Switch to the table owner's userid, so that any index functions are run
-	 * as that user.  Also lock down security-restricted operations and
-	 * arrange to make GUC variable changes local to this command. (This is
-	 * unnecessary, but harmless, for lazy VACUUM.)
+	 * Prevent index functions from doing what they are not supposed to.
 	 */
-	GetUserIdAndSecContext(&save_userid, &save_sec_context);
-	SetUserIdAndSecContext(rel->rd_rel->relowner,
-						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
-	save_nestlevel = NewGUCNestLevel();
-	RestrictSearchPath();
+	enable_index_build_security(rel->rd_rel->relowner, &ibsec);
 
 	/*
 	 * If PROCESS_MAIN is set (the default), it's time to vacuum the main
@@ -2310,11 +2302,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
 			table_relation_vacuum(rel, &params, bstrategy);
 	}
 
-	/* Roll back any GUC changes executed by index functions */
-	AtEOXact_GUC(false, save_nestlevel);
-
-	/* Restore userid and security context */
-	SetUserIdAndSecContext(save_userid, save_sec_context);
+	/* Relax the restrictions imposed above. */
+	disable_index_build_security(&ibsec);
 
 	/* all done with this class, but hold lock until commit */
 	if (rel)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 9aee8226347..dd9ae8119e5 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -172,6 +172,16 @@ extern void reindex_index(const ReindexStmt *stmt, Oid indexId,
 extern bool reindex_relation(const ReindexStmt *stmt, Oid relid, int flags,
 							 const ReindexParams *params);
 
+typedef struct IndexBuildSecurity
+{
+	Oid			userid;
+	int			sec_context;
+	int			nestlevel;
+} IndexBuildSecurity;
+
+extern void enable_index_build_security(Oid userid, IndexBuildSecurity *sec);
+extern void disable_index_build_security(IndexBuildSecurity *sec);
+
 extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 25ac7079099..0ec534d15d7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ IndexAttachInfo
 IndexAttrBitmapKind
 IndexBuildCallback
 IndexBuildResult
+IndexBuildSecurity
 IndexBulkDeleteCallback
 IndexBulkDeleteResult
 IndexClause
-- 
2.52.0


--=-=-=
Content-Type: text/x-diff
Content-Disposition: attachment;
 filename=v02-0006-Use-separate-transactions-for-catalog-changes.patch



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


end of thread, other threads:[~2026-07-15 08:37 UTC | newest]

Thread overview: 220+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2023-07-26 10:49 [PATCH v3 1/7] Row pattern recognition patch for raw parser. Tatsuo Ishii <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-06-16 11:54 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>
2026-07-15 08:37 [PATCH 5/8] Simplify the way restrictions are imposed on index functions. Antonin Houska <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox