agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v3] Fix DROP PROPERTY GRAPH "unsupported object class" error
92+ messages / 2 participants
[nested] [flat]

* [PATCH v3] Fix DROP PROPERTY GRAPH "unsupported object class" error
@ 2026-04-23 09:27  Bertrand Drouvot <[email protected]>
  0 siblings, 0 replies; 92+ messages in thread

From: Bertrand Drouvot @ 2026-04-23 09:27 UTC (permalink / raw)

getObjectTypeDescription() and getObjectIdentityParts() are missing switch cases
for PropgraphElementLabelRelationId and PropgraphLabelPropertyRelationId, causing
them to hit the default case and error out with "unsupported object class".

During DROP PROPERTY GRAPH, this manifests when an event trigger is active,
because pg_event_trigger_ddl_commands() calls these functions. The same code
paths are also reachable via pg_identify_object() and pg_identify_object_as_address().

This commit adds the missing cases.

Test coverage is added in object_address.sql for these two catalog types and
in create_property_graph.sql covering all property graph object types.

A test in event_trigger.sql verifies that DROP PROPERTY GRAPH works correctly
when an event trigger is active.

Author: Bertrand Drouvot <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Reviewed-by: Ashutosh Bapat <[email protected]>
Discussion: https://postgr.es/m/aej1DkLwhyZWmtxJ@bdtpg
---
 src/backend/catalog/objectaddress.c           | 93 +++++++++++++++++++
 .../expected/create_property_graph.out        | 78 ++++++++++++++++
 src/test/regress/expected/event_trigger.out   | 17 ++++
 src/test/regress/expected/object_address.out  | 10 +-
 .../regress/sql/create_property_graph.sql     | 23 +++++
 src/test/regress/sql/event_trigger.sql        | 13 +++
 src/test/regress/sql/object_address.sql       |  6 +-
 7 files changed, 238 insertions(+), 2 deletions(-)
  14.7% src/backend/catalog/
  75.5% src/test/regress/expected/
   9.7% src/test/regress/sql/

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c1862809577..e2d6d8f71f6 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -4901,10 +4901,18 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok)
 			appendStringInfoString(&buffer, "policy");
 			break;
 
+		case PropgraphElementLabelRelationId:
+			appendStringInfoString(&buffer, "property graph element label");
+			break;
+
 		case PropgraphElementRelationId:
 			appendStringInfoString(&buffer, "property graph element");
 			break;
 
+		case PropgraphLabelPropertyRelationId:
+			appendStringInfoString(&buffer, "property graph label property");
+			break;
+
 		case PropgraphLabelRelationId:
 			appendStringInfoString(&buffer, "property graph label");
 			break;
@@ -6161,6 +6169,49 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case PropgraphElementLabelRelationId:
+			{
+				Relation	ellabelDesc;
+				ScanKeyData skey[1];
+				SysScanDesc ellabelscan;
+				HeapTuple	tup;
+				Form_pg_propgraph_element_label pgelform;
+				ObjectAddress oa;
+
+				ellabelDesc = table_open(PropgraphElementLabelRelationId, AccessShareLock);
+				ScanKeyInit(&skey[0],
+							Anum_pg_propgraph_element_label_oid,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				ellabelscan = systable_beginscan(ellabelDesc,
+												 PropgraphElementLabelObjectIndexId,
+												 true, NULL, 1, skey);
+
+				tup = systable_getnext(ellabelscan);
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for element label %u",
+							 object->objectId);
+
+					systable_endscan(ellabelscan);
+					table_close(ellabelDesc, AccessShareLock);
+					break;
+				}
+
+				pgelform = (Form_pg_propgraph_element_label) GETSTRUCT(tup);
+
+				ObjectAddressSet(oa, PropgraphElementRelationId, pgelform->pgelelid);
+
+				appendStringInfoString(&buffer, getObjectIdentityParts(&oa, objname,
+																	   objargs, false));
+
+				systable_endscan(ellabelscan);
+				table_close(ellabelDesc, AccessShareLock);
+				break;
+			}
+
 		case PropgraphElementRelationId:
 			{
 				HeapTuple	tup;
@@ -6184,6 +6235,48 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case PropgraphLabelPropertyRelationId:
+			{
+				Relation	lblpropDesc;
+				ScanKeyData skey[1];
+				SysScanDesc lblpropscan;
+				HeapTuple	tup;
+				Form_pg_propgraph_label_property plpform;
+				ObjectAddress oa;
+
+				lblpropDesc = table_open(PropgraphLabelPropertyRelationId,
+										 AccessShareLock);
+				ScanKeyInit(&skey[0],
+							Anum_pg_propgraph_label_property_oid,
+							BTEqualStrategyNumber, F_OIDEQ,
+							ObjectIdGetDatum(object->objectId));
+
+				lblpropscan = systable_beginscan(lblpropDesc, PropgraphLabelPropertyObjectIndexId,
+												 true, NULL, 1, skey);
+
+				tup = systable_getnext(lblpropscan);
+				if (!HeapTupleIsValid(tup))
+				{
+					if (!missing_ok)
+						elog(ERROR, "could not find tuple for label property %u",
+							 object->objectId);
+
+					systable_endscan(lblpropscan);
+					table_close(lblpropDesc, AccessShareLock);
+					break;
+				}
+
+				plpform = (Form_pg_propgraph_label_property) GETSTRUCT(tup);
+
+				ObjectAddressSet(oa, PropgraphElementLabelRelationId, plpform->plpellabelid);
+				appendStringInfoString(&buffer, getObjectIdentityParts(&oa, objname,
+																	   objargs, false));
+
+				systable_endscan(lblpropscan);
+				table_close(lblpropDesc, AccessShareLock);
+				break;
+			}
+
 		case PropgraphLabelRelationId:
 			{
 				HeapTuple	tup;
diff --git a/src/test/regress/expected/create_property_graph.out b/src/test/regress/expected/create_property_graph.out
index bc9a596ec89..eb9e17fb727 100644
--- a/src/test/regress/expected/create_property_graph.out
+++ b/src/test/regress/expected/create_property_graph.out
@@ -752,6 +752,84 @@ SELECT (pg_identify_object(classid, objid, objsubid)).*
  type                    | create_property_graph_tests | g2   | create_property_graph_tests.g2
 (21 rows)
 
+-- test object address functions with recursive dependency walk
+-- covers all property graph object types including indirect dependents
+SELECT *
+FROM (
+    WITH RECURSIVE deps (classid, objid, objsubid, refclassid, refobjid, refobjsubid) AS (
+        SELECT classid, objid, objsubid,
+               refclassid, refobjid, refobjsubid
+        FROM pg_depend
+        WHERE refclassid = 'pg_class'::regclass AND
+              refobjid = 'create_property_graph_tests.gt'::regclass
+        UNION ALL
+        SELECT d.classid, d.objid, d.objsubid,
+               d.refclassid, d.refobjid, d.refobjsubid
+        FROM pg_depend d
+        JOIN deps dp ON d.refclassid = dp.classid AND d.refobjid = dp.objid AND d.refobjsubid = dp.objsubid
+    )
+    SELECT pg_describe_object(classid, objid, objsubid) as obj,
+           pg_identify_object(classid, objid, objsubid) as ident,
+           pg_identify_object_as_address(classid, objid, objsubid) as addr
+    FROM deps
+) s
+ORDER BY obj COLLATE "C", ident::text COLLATE "C";
+                           obj                            |                                   ident                                    |                                    addr                                    
+----------------------------------------------------------+----------------------------------------------------------------------------+----------------------------------------------------------------------------
+ edge e of property graph gt                              | ("property graph element",,,"e of create_property_graph_tests.gt")         | ("property graph element","{create_property_graph_tests,gt,e}",{})
+ edge e of property graph gt                              | ("property graph element",,,"e of create_property_graph_tests.gt")         | ("property graph element","{create_property_graph_tests,gt,e}",{})
+ edge e of property graph gt                              | ("property graph element",,,"e of create_property_graph_tests.gt")         | ("property graph element","{create_property_graph_tests,gt,e}",{})
+ label e of edge e of property graph gt                   | ("property graph element label",,,"e of create_property_graph_tests.gt")   | ("property graph element label","{create_property_graph_tests,gt,e}",{})
+ label e of edge e of property graph gt                   | ("property graph element label",,,"e of create_property_graph_tests.gt")   | ("property graph element label","{create_property_graph_tests,gt,e}",{})
+ label e of edge e of property graph gt                   | ("property graph element label",,,"e of create_property_graph_tests.gt")   | ("property graph element label","{create_property_graph_tests,gt,e}",{})
+ label e of edge e of property graph gt                   | ("property graph element label",,,"e of create_property_graph_tests.gt")   | ("property graph element label","{create_property_graph_tests,gt,e}",{})
+ label e of property graph gt                             | ("property graph label",,,"e of create_property_graph_tests.gt")           | ("property graph label","{create_property_graph_tests,gt,e}",{})
+ label v1 of property graph gt                            | ("property graph label",,,"v1 of create_property_graph_tests.gt")          | ("property graph label","{create_property_graph_tests,gt,v1}",{})
+ label v1 of vertex v1 of property graph gt               | ("property graph element label",,,"v1 of create_property_graph_tests.gt")  | ("property graph element label","{create_property_graph_tests,gt,v1}",{})
+ label v1 of vertex v1 of property graph gt               | ("property graph element label",,,"v1 of create_property_graph_tests.gt")  | ("property graph element label","{create_property_graph_tests,gt,v1}",{})
+ label v2 of property graph gt                            | ("property graph label",,,"v2 of create_property_graph_tests.gt")          | ("property graph label","{create_property_graph_tests,gt,v2}",{})
+ label v2 of vertex v2 of property graph gt               | ("property graph element label",,,"v2 of create_property_graph_tests.gt")  | ("property graph element label","{create_property_graph_tests,gt,v2}",{})
+ label v2 of vertex v2 of property graph gt               | ("property graph element label",,,"v2 of create_property_graph_tests.gt")  | ("property graph element label","{create_property_graph_tests,gt,v2}",{})
+ property a of label v1 of vertex v1 of property graph gt | ("property graph label property",,,"v1 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v1}",{})
+ property a of label v1 of vertex v1 of property graph gt | ("property graph label property",,,"v1 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v1}",{})
+ property a of label v1 of vertex v1 of property graph gt | ("property graph label property",,,"v1 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v1}",{})
+ property a of property graph gt                          | ("property graph property",,,"a of create_property_graph_tests.gt")        | ("property graph property","{create_property_graph_tests,gt,a}",{})
+ property b of label v1 of vertex v1 of property graph gt | ("property graph label property",,,"v1 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v1}",{})
+ property b of label v1 of vertex v1 of property graph gt | ("property graph label property",,,"v1 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v1}",{})
+ property b of label v1 of vertex v1 of property graph gt | ("property graph label property",,,"v1 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v1}",{})
+ property b of property graph gt                          | ("property graph property",,,"b of create_property_graph_tests.gt")        | ("property graph property","{create_property_graph_tests,gt,b}",{})
+ property c of label e of edge e of property graph gt     | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property c of label e of edge e of property graph gt     | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property c of label e of edge e of property graph gt     | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property c of label e of edge e of property graph gt     | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property c of label e of edge e of property graph gt     | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property c of property graph gt                          | ("property graph property",,,"c of create_property_graph_tests.gt")        | ("property graph property","{create_property_graph_tests,gt,c}",{})
+ property k1 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k1 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k1 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k1 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k1 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k1 of property graph gt                         | ("property graph property",,,"k1 of create_property_graph_tests.gt")       | ("property graph property","{create_property_graph_tests,gt,k1}",{})
+ property k2 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k2 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k2 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k2 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k2 of label e of edge e of property graph gt    | ("property graph label property",,,"e of create_property_graph_tests.gt")  | ("property graph label property","{create_property_graph_tests,gt,e}",{})
+ property k2 of property graph gt                         | ("property graph property",,,"k2 of create_property_graph_tests.gt")       | ("property graph property","{create_property_graph_tests,gt,k2}",{})
+ property m of label v2 of vertex v2 of property graph gt | ("property graph label property",,,"v2 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v2}",{})
+ property m of label v2 of vertex v2 of property graph gt | ("property graph label property",,,"v2 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v2}",{})
+ property m of label v2 of vertex v2 of property graph gt | ("property graph label property",,,"v2 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v2}",{})
+ property m of property graph gt                          | ("property graph property",,,"m of create_property_graph_tests.gt")        | ("property graph property","{create_property_graph_tests,gt,m}",{})
+ property n of label v2 of vertex v2 of property graph gt | ("property graph label property",,,"v2 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v2}",{})
+ property n of label v2 of vertex v2 of property graph gt | ("property graph label property",,,"v2 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v2}",{})
+ property n of label v2 of vertex v2 of property graph gt | ("property graph label property",,,"v2 of create_property_graph_tests.gt") | ("property graph label property","{create_property_graph_tests,gt,v2}",{})
+ property n of property graph gt                          | ("property graph property",,,"n of create_property_graph_tests.gt")        | ("property graph property","{create_property_graph_tests,gt,n}",{})
+ type gt                                                  | (type,create_property_graph_tests,gt,create_property_graph_tests.gt)       | (type,{create_property_graph_tests.gt},{})
+ type gt[]                                                | (type,create_property_graph_tests,_gt,create_property_graph_tests.gt[])    | (type,{create_property_graph_tests.gt[]},{})
+ vertex v1 of property graph gt                           | ("property graph element",,,"v1 of create_property_graph_tests.gt")        | ("property graph element","{create_property_graph_tests,gt,v1}",{})
+ vertex v2 of property graph gt                           | ("property graph element",,,"v2 of create_property_graph_tests.gt")        | ("property graph element","{create_property_graph_tests,gt,v2}",{})
+(52 rows)
+
 \a\t
 SELECT pg_get_propgraphdef('g2'::regclass);
 CREATE PROPERTY GRAPH create_property_graph_tests.g2
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 065f586310f..ed8d5df397e 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -173,6 +173,23 @@ NOTICE:  test_event_trigger: ddl_command_end CREATE USER MAPPING
 alter default privileges for role regress_evt_user
  revoke delete on tables from regress_evt_user;
 NOTICE:  test_event_trigger: ddl_command_end ALTER DEFAULT PRIVILEGES
+-- DROP PROPERTY GRAPH should work with event trigger in place
+CREATE TABLE t1x (a int PRIMARY KEY, b text);
+NOTICE:  test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE:  test_event_trigger: ddl_command_end CREATE TABLE
+CREATE TABLE t2x (i int PRIMARY KEY, j text);
+NOTICE:  test_event_trigger: ddl_command_start CREATE TABLE
+NOTICE:  test_event_trigger: ddl_command_end CREATE TABLE
+CREATE PROPERTY GRAPH gx
+	VERTEX TABLES (
+		t1x KEY (a) LABEL l1 PROPERTIES (b AS p1),
+		t2x KEY (i) LABEL l2 PROPERTIES (j AS p1)
+);
+NOTICE:  test_event_trigger: ddl_command_end CREATE PROPERTY GRAPH
+DROP PROPERTY GRAPH gx;
+NOTICE:  test_event_trigger: ddl_command_end DROP PROPERTY GRAPH
+DROP TABLE t1x, t2x;
+NOTICE:  test_event_trigger: ddl_command_end DROP TABLE
 -- alter owner to non-superuser should fail
 alter event trigger regress_event_trigger owner to regress_evt_user;
 ERROR:  permission denied to change owner of event trigger "regress_event_trigger"
diff --git a/src/test/regress/expected/object_address.out b/src/test/regress/expected/object_address.out
index 97227d67a54..5e53f0b092e 100644
--- a/src/test/regress/expected/object_address.out
+++ b/src/test/regress/expected/object_address.out
@@ -67,7 +67,9 @@ DECLARE
 BEGIN
     FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'),
         ('toast table column'), ('view column'), ('materialized view column'),
-        ('property graph element'), ('property graph label'), ('property graph property')
+        ('property graph element'), ('property graph element label'),
+        ('property graph label'), ('property graph label property'),
+        ('property graph property')
     LOOP
         BEGIN
             PERFORM pg_get_object_address(objtype, '{one}', '{}');
@@ -84,7 +86,9 @@ WARNING:  error for toast table column: unsupported object type "toast table col
 WARNING:  error for view column: unsupported object type "view column"
 WARNING:  error for materialized view column: unsupported object type "materialized view column"
 WARNING:  error for property graph element: unsupported object type "property graph element"
+WARNING:  error for property graph element label: unrecognized object type "property graph element label"
 WARNING:  error for property graph label: unsupported object type "property graph label"
+WARNING:  error for property graph label property: unrecognized object type "property graph label property"
 WARNING:  error for property graph property: unsupported object type "property graph property"
 -- miscellaneous other errors
 select * from pg_get_object_address('operator of access method', '{btree,integer_ops,1}', '{int4,bool}');
@@ -593,7 +597,9 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_parameter_acl'::regclass, 0, 0), -- no parameter ACL
     ('pg_policy'::regclass, 0, 0), -- no policy
     ('pg_propgraph_element'::regclass, 0, 0), -- no property graph element
+    ('pg_propgraph_element_label'::regclass, 0, 0), -- no property graph element label
     ('pg_propgraph_label'::regclass, 0, 0), -- no property graph label
+    ('pg_propgraph_label_property'::regclass, 0, 0), -- no property graph label property
     ('pg_propgraph_property'::regclass, 0, 0), -- no property graph property
     ('pg_publication'::regclass, 0, 0), -- no publication
     ('pg_publication_namespace'::regclass, 0, 0), -- no publication namespace
@@ -653,6 +659,8 @@ ORDER BY objects.classid, objects.objid, objects.objsubid;
 ("(""parameter ACL"",,,)")|("(""parameter ACL"",,)")|NULL
 ("(""property graph element"",,,)")|("(""property graph element"",,)")|NULL
 ("(""property graph label"",,,)")|("(""property graph label"",,)")|NULL
+("(""property graph element label"",,,)")|("(""property graph element label"",,)")|NULL
 ("(""property graph property"",,,)")|("(""property graph property"",,)")|NULL
+("(""property graph label property"",,,)")|("(""property graph label property"",,)")|NULL
 -- restore normal output mode
 \a\t
diff --git a/src/test/regress/sql/create_property_graph.sql b/src/test/regress/sql/create_property_graph.sql
index 241f93df302..9608dd5c4af 100644
--- a/src/test/regress/sql/create_property_graph.sql
+++ b/src/test/regress/sql/create_property_graph.sql
@@ -298,6 +298,29 @@ SELECT (pg_identify_object(classid, objid, objsubid)).*
           refobjid = 'create_property_graph_tests.g2'::regclass
     ORDER BY 1, 2, 3, 4;
 
+-- test object address functions with recursive dependency walk
+-- covers all property graph object types including indirect dependents
+SELECT *
+FROM (
+    WITH RECURSIVE deps (classid, objid, objsubid, refclassid, refobjid, refobjsubid) AS (
+        SELECT classid, objid, objsubid,
+               refclassid, refobjid, refobjsubid
+        FROM pg_depend
+        WHERE refclassid = 'pg_class'::regclass AND
+              refobjid = 'create_property_graph_tests.gt'::regclass
+        UNION ALL
+        SELECT d.classid, d.objid, d.objsubid,
+               d.refclassid, d.refobjid, d.refobjsubid
+        FROM pg_depend d
+        JOIN deps dp ON d.refclassid = dp.classid AND d.refobjid = dp.objid AND d.refobjsubid = dp.objsubid
+    )
+    SELECT pg_describe_object(classid, objid, objsubid) as obj,
+           pg_identify_object(classid, objid, objsubid) as ident,
+           pg_identify_object_as_address(classid, objid, objsubid) as addr
+    FROM deps
+) s
+ORDER BY obj COLLATE "C", ident::text COLLATE "C";
+
 \a\t
 SELECT pg_get_propgraphdef('g2'::regclass);
 SELECT pg_get_propgraphdef('g3'::regclass);
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index 32e9bb58c5e..271f443378b 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -143,6 +143,19 @@ create user mapping for regress_evt_user server useless_server;
 alter default privileges for role regress_evt_user
  revoke delete on tables from regress_evt_user;
 
+-- DROP PROPERTY GRAPH should work with event trigger in place
+CREATE TABLE t1x (a int PRIMARY KEY, b text);
+CREATE TABLE t2x (i int PRIMARY KEY, j text);
+
+CREATE PROPERTY GRAPH gx
+	VERTEX TABLES (
+		t1x KEY (a) LABEL l1 PROPERTIES (b AS p1),
+		t2x KEY (i) LABEL l2 PROPERTIES (j AS p1)
+);
+
+DROP PROPERTY GRAPH gx;
+DROP TABLE t1x, t2x;
+
 -- alter owner to non-superuser should fail
 alter event trigger regress_event_trigger owner to regress_evt_user;
 
diff --git a/src/test/regress/sql/object_address.sql b/src/test/regress/sql/object_address.sql
index 1bbe9457c1c..27109d421c7 100644
--- a/src/test/regress/sql/object_address.sql
+++ b/src/test/regress/sql/object_address.sql
@@ -67,7 +67,9 @@ DECLARE
 BEGIN
     FOR objtype IN VALUES ('toast table'), ('index column'), ('sequence column'),
         ('toast table column'), ('view column'), ('materialized view column'),
-        ('property graph element'), ('property graph label'), ('property graph property')
+        ('property graph element'), ('property graph element label'),
+        ('property graph label'), ('property graph label property'),
+        ('property graph property')
     LOOP
         BEGIN
             PERFORM pg_get_object_address(objtype, '{one}', '{}');
@@ -282,7 +284,9 @@ WITH objects (classid, objid, objsubid) AS (VALUES
     ('pg_parameter_acl'::regclass, 0, 0), -- no parameter ACL
     ('pg_policy'::regclass, 0, 0), -- no policy
     ('pg_propgraph_element'::regclass, 0, 0), -- no property graph element
+    ('pg_propgraph_element_label'::regclass, 0, 0), -- no property graph element label
     ('pg_propgraph_label'::regclass, 0, 0), -- no property graph label
+    ('pg_propgraph_label_property'::regclass, 0, 0), -- no property graph label property
     ('pg_propgraph_property'::regclass, 0, 0), -- no property graph property
     ('pg_publication'::regclass, 0, 0), -- no publication
     ('pg_publication_namespace'::regclass, 0, 0), -- no publication namespace
-- 
2.34.1


--R84XrR4Hgw0dH3Ec--





^ permalink  raw  reply  [nested|flat] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ 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; 92+ 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] 92+ messages in thread


end of thread, other threads:[~2026-07-15 08:37 UTC | newest]

Thread overview: 92+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-04-23 09:27 [PATCH v3] Fix DROP PROPERTY GRAPH "unsupported object class" error Bertrand Drouvot <[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]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox