| From: | Roberto C(dot) Sánchez <roberto(at)debian(dot)org> | 
|---|---|
| To: | pgsql-hackers(at)lists(dot)postgresql(dot)org | 
| Subject: | Re: Request for assistance to backport CVE-2022-1552 fixes to 9.6 and 9.4 | 
| Date: | 2022-07-27 15:52:47 | 
| Message-ID: | YuFfT9Dyp1aNbBKP@connexer.com | 
| Views: | Whole Thread | Raw Message | Download mbox | Resend email | 
| Thread: | |
| Lists: | pgsql-hackers | 
Hello pgsql-hackers,
Is there anyone willing to review the patches that I prepared?  I'd have
substatntially more confidence in the patches with a review from an
upstream developer who is familiar with the code.
Regards,
-Roberto
On Mon, Jul 04, 2022 at 06:06:58PM -0400, Roberto C. Sánchez wrote:
> On Wed, Jun 08, 2022 at 05:31:22PM -0400, Roberto C. Sánchez wrote:
> > On Wed, Jun 08, 2022 at 04:15:47PM -0400, Tom Lane wrote:
> > > Roberto =?iso-8859-1?Q?C=2E_S=E1nchez?= <roberto(at)debian(dot)org> writes:
> > > > I am investigating backporting the fixes for CVE-2022-1552 to 9.6 and
> > > > 9.4 as part of Debian LTS and Extended LTS.  I am aware that these
> > > > releases are no longer supported upstream, but I have made an attempt at
> > > > adapting commits ef792f7856dea2576dcd9cab92b2b05fe955696b and
> > > > f26d5702857a9c027f84850af48b0eea0f3aa15c from the REL_10_STABLE branch.
> > > > I would appreciate a review of the attached patches and any comments on
> > > > things that may have been missed and/or adapted improperly.
> > > 
> > > FWIW, I would not recommend being in a huge hurry to back-port those
> > > changes, pending the outcome of this discussion:
> > > 
> > > https://www.postgresql.org/message-id/flat/f8a4105f076544c180a87ef0c4822352%40stmuk.bayern.de
> > > 
> > Thanks for the pointer.
> > 
> > > We're going to have to tweak that code somehow, and it's not yet
> > > entirely clear how.
> > > 
> > I will monitor the discussion to see what comes of it.
> > 
> Based on the discussion in the other thread, I have made an attempt to
> backport commit 88b39e61486a8925a3861d50c306a51eaa1af8d6 to 9.6 and 9.4.
> The only significant change that I had to make was to add the full
> function signatures for the REVOKE/GRANT in the citext test.
> 
> One question that I had about the change as committed is whether a
> REVOKE is needed on s.citext_ne, like so:
> 
> REVOKE ALL ON FUNCTION s.citext_ne FROM PUBLIC;
> 
> Or (for pre-10):
> 
> REVOKE ALL ON FUNCTION s.citext_ne(s.citext, s.citext) FROM PUBLIC;
> 
> I ask because the comment immediately preceding the sequence of REVOKEs
> includes the comment "Revoke all conceivably-relevant ACLs within the
> extension."  I am not especially knowledgable about deep internals, but
> that function seems like it would belong in the same group with the
> others.
> 
> In any event, would someone be willing to review the attached patches
> for correctness?  I would like to shortly publish updates to 9.6 and 9.4
> in Debian and a review would be most appreciated.
> 
> Regards,
> 
> -Roberto
> 
> -- 
> Roberto C. Sánchez
> From ef792f7856dea2576dcd9cab92b2b05fe955696b Mon Sep 17 00:00:00 2001
> From: Noah Misch <noah(at)leadboat(dot)com>
> Date: Mon, 9 May 2022 08:35:08 -0700
> Subject: [PATCH] Make relation-enumerating operations be security-restricted
>  operations.
> 
> When a feature enumerates relations and runs functions associated with
> all found relations, the feature's user shall not need to trust every
> user having permission to create objects.  BRIN-specific functionality
> in autovacuum neglected to account for this, as did pg_amcheck and
> CLUSTER.  An attacker having permission to create non-temp objects in at
> least one schema could execute arbitrary SQL functions under the
> identity of the bootstrap superuser.  CREATE INDEX (not a
> relation-enumerating operation) and REINDEX protected themselves too
> late.  This change extends to the non-enumerating amcheck interface.
> Back-patch to v10 (all supported versions).
> 
> Sergey Shinderuk, reviewed (in earlier versions) by Alexander Lakhin.
> Reported by Alexander Lakhin.
> 
> Security: CVE-2022-1552
> ---
>  src/backend/access/brin/brin.c           |   30 ++++++++++++++++-
>  src/backend/catalog/index.c              |   41 +++++++++++++++++------
>  src/backend/commands/cluster.c           |   35 ++++++++++++++++----
>  src/backend/commands/indexcmds.c         |   53 +++++++++++++++++++++++++++++--
>  src/backend/utils/init/miscinit.c        |   24 ++++++++------
>  src/test/regress/expected/privileges.out |   42 ++++++++++++++++++++++++
>  src/test/regress/sql/privileges.sql      |   36 +++++++++++++++++++++
>  7 files changed, 231 insertions(+), 30 deletions(-)
> 
> --- a/src/backend/access/brin/brin.c
> +++ b/src/backend/access/brin/brin.c
> @@ -28,6 +28,7 @@
>  #include "pgstat.h"
>  #include "storage/bufmgr.h"
>  #include "storage/freespace.h"
> +#include "utils/guc.h"
>  #include "utils/index_selfuncs.h"
>  #include "utils/memutils.h"
>  #include "utils/rel.h"
> @@ -786,6 +787,9 @@
>  	Oid			heapoid;
>  	Relation	indexRel;
>  	Relation	heapRel;
> +	Oid			save_userid;
> +	int			save_sec_context;
> +	int			save_nestlevel;
>  	double		numSummarized = 0;
>  
>  	if (RecoveryInProgress())
> @@ -799,10 +803,28 @@
>  	 * passed indexoid isn't an index then IndexGetRelation() will fail.
>  	 * Rather than emitting a not-very-helpful error message, postpone
>  	 * complaining, expecting that the is-it-an-index test below will fail.
> +	 *
> +	 * unlike brin_summarize_range(), autovacuum never calls this.  hence, we
> +	 * don't switch userid.
>  	 */
>  	heapoid = IndexGetRelation(indexoid, true);
>  	if (OidIsValid(heapoid))
> +	{
>  		heapRel = heap_open(heapoid, ShareUpdateExclusiveLock);
> +
> +		/*
> +		 * Autovacuum calls us.  For its benefit, switch to the table owner's
> +		 * userid, so that any index functions are run as that user.  Also
> +		 * lock down security-restricted operations and arrange to make GUC
> +		 * variable changes local to this command.  This is harmless, albeit
> +		 * unnecessary, when called from SQL, because we fail shortly if the
> +		 * user does not own the index.
> +		 */
> +		GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +		SetUserIdAndSecContext(heapRel->rd_rel->relowner,
> +							   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +		save_nestlevel = NewGUCNestLevel();
> +	}
>  	else
>  		heapRel = NULL;
>  
> @@ -817,7 +839,7 @@
>  						RelationGetRelationName(indexRel))));
>  
>  	/* User must own the index (comparable to privileges needed for VACUUM) */
> -	if (!pg_class_ownercheck(indexoid, GetUserId()))
> +	if (heapRel != NULL && !pg_class_ownercheck(indexoid, save_userid))
>  		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
>  					   RelationGetRelationName(indexRel));
>  
> @@ -835,6 +857,12 @@
>  	/* OK, do it */
>  	brinsummarize(indexRel, heapRel, &numSummarized, NULL);
>  
> +	/* Roll back any GUC changes executed by index functions */
> +	AtEOXact_GUC(false, save_nestlevel);
> +
> +	/* Restore userid and security context */
> +	SetUserIdAndSecContext(save_userid, save_sec_context);
> +
>  	relation_close(indexRel, ShareUpdateExclusiveLock);
>  	relation_close(heapRel, ShareUpdateExclusiveLock);
>  
> --- a/src/backend/catalog/index.c
> +++ b/src/backend/catalog/index.c
> @@ -2908,7 +2908,17 @@
>  
>  	/* Open and lock the parent heap relation */
>  	heapRelation = heap_open(heapId, ShareUpdateExclusiveLock);
> -	/* And the target index relation */
> +
> +	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations and
> +	 * arrange to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
> +						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	save_nestlevel = NewGUCNestLevel();
> +
>  	indexRelation = index_open(indexId, RowExclusiveLock);
>  
>  	/*
> @@ -2922,16 +2932,6 @@
>  	indexInfo->ii_Concurrent = true;
>  
>  	/*
> -	 * Switch to the table owner's userid, so that any index functions are run
> -	 * as that user.  Also lock down security-restricted operations and
> -	 * arrange to make GUC variable changes local to this command.
> -	 */
> -	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> -	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
> -						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> -	save_nestlevel = NewGUCNestLevel();
> -
> -	/*
>  	 * Scan the index and gather up all the TIDs into a tuplesort object.
>  	 */
>  	ivinfo.index = indexRelation;
> @@ -3395,6 +3395,9 @@
>  	Relation	iRel,
>  				heapRelation;
>  	Oid			heapId;
> +	Oid			save_userid;
> +	int			save_sec_context;
> +	int			save_nestlevel;
>  	IndexInfo  *indexInfo;
>  	volatile bool skipped_constraint = false;
>  	PGRUsage	ru0;
> @@ -3409,6 +3412,16 @@
>  	heapRelation = heap_open(heapId, ShareLock);
>  
>  	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations and
> +	 * arrange to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
> +						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	save_nestlevel = NewGUCNestLevel();
> +
> +	/*
>  	 * Open the target index relation and get an exclusive lock on it, to
>  	 * ensure that no one else is touching this particular index.
>  	 */
> @@ -3550,6 +3563,12 @@
>  				 errdetail_internal("%s",
>  						   pg_rusage_show(&ru0))));
>  
> +	/* Roll back any GUC changes executed by index functions */
> +	AtEOXact_GUC(false, save_nestlevel);
> +
> +	/* Restore userid and security context */
> +	SetUserIdAndSecContext(save_userid, save_sec_context);
> +
>  	/* Close rels, but keep locks */
>  	index_close(iRel, NoLock);
>  	heap_close(heapRelation, NoLock);
> --- a/src/backend/commands/cluster.c
> +++ b/src/backend/commands/cluster.c
> @@ -44,6 +44,7 @@
>  #include "storage/smgr.h"
>  #include "utils/acl.h"
>  #include "utils/fmgroids.h"
> +#include "utils/guc.h"
>  #include "utils/inval.h"
>  #include "utils/lsyscache.h"
>  #include "utils/memutils.h"
> @@ -260,6 +261,9 @@
>  cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
>  {
>  	Relation	OldHeap;
> +	Oid			save_userid;
> +	int			save_sec_context;
> +	int			save_nestlevel;
>  
>  	/* Check for user-requested abort. */
>  	CHECK_FOR_INTERRUPTS();
> @@ -277,6 +281,16 @@
>  		return;
>  
>  	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations and
> +	 * arrange to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
> +						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	save_nestlevel = NewGUCNestLevel();
> +
> +	/*
>  	 * Since we may open a new transaction for each relation, we have to check
>  	 * that the relation still is what we think it is.
>  	 *
> @@ -290,10 +304,10 @@
>  		Form_pg_index indexForm;
>  
>  		/* Check that the user still owns the relation */
> -		if (!pg_class_ownercheck(tableOid, GetUserId()))
> +		if (!pg_class_ownercheck(tableOid, save_userid))
>  		{
>  			relation_close(OldHeap, AccessExclusiveLock);
> -			return;
> +			goto out;
>  		}
>  
>  		/*
> @@ -307,7 +321,7 @@
>  		if (RELATION_IS_OTHER_TEMP(OldHeap))
>  		{
>  			relation_close(OldHeap, AccessExclusiveLock);
> -			return;
> +			goto out;
>  		}
>  
>  		if (OidIsValid(indexOid))
> @@ -318,7 +332,7 @@
>  			if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
>  			{
>  				relation_close(OldHeap, AccessExclusiveLock);
> -				return;
> +				goto out;
>  			}
>  
>  			/*
> @@ -328,14 +342,14 @@
>  			if (!HeapTupleIsValid(tuple))		/* probably can't happen */
>  			{
>  				relation_close(OldHeap, AccessExclusiveLock);
> -				return;
> +				goto out;
>  			}
>  			indexForm = (Form_pg_index) GETSTRUCT(tuple);
>  			if (!indexForm->indisclustered)
>  			{
>  				ReleaseSysCache(tuple);
>  				relation_close(OldHeap, AccessExclusiveLock);
> -				return;
> +				goto out;
>  			}
>  			ReleaseSysCache(tuple);
>  		}
> @@ -389,7 +403,7 @@
>  		!RelationIsPopulated(OldHeap))
>  	{
>  		relation_close(OldHeap, AccessExclusiveLock);
> -		return;
> +		goto out;
>  	}
>  
>  	/*
> @@ -404,6 +418,13 @@
>  	rebuild_relation(OldHeap, indexOid, verbose);
>  
>  	/* NB: rebuild_relation does heap_close() on OldHeap */
> +
> +out:
> +	/* Roll back any GUC changes executed by index functions */
> +	AtEOXact_GUC(false, save_nestlevel);
> +
> +	/* Restore userid and security context */
> +	SetUserIdAndSecContext(save_userid, save_sec_context);
>  }
>  
>  /*
> --- a/src/backend/commands/indexcmds.c
> +++ b/src/backend/commands/indexcmds.c
> @@ -49,6 +49,7 @@
>  #include "utils/acl.h"
>  #include "utils/builtins.h"
>  #include "utils/fmgroids.h"
> +#include "utils/guc.h"
>  #include "utils/inval.h"
>  #include "utils/lsyscache.h"
>  #include "utils/memutils.h"
> @@ -339,8 +340,13 @@
>  	LOCKTAG		heaplocktag;
>  	LOCKMODE	lockmode;
>  	Snapshot	snapshot;
> +	Oid			root_save_userid;
> +	int			root_save_sec_context;
> +	int			root_save_nestlevel;
>  	int			i;
>  
> +	root_save_nestlevel = NewGUCNestLevel();
> +
>  	/*
>  	 * Force non-concurrent build on temporary relations, even if CONCURRENTLY
>  	 * was requested.  Other backends can't access a temporary relation, so
> @@ -381,6 +387,15 @@
>  	lockmode = concurrent ? ShareUpdateExclusiveLock : ShareLock;
>  	rel = heap_open(relationId, lockmode);
>  
> +	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations.  We
> +	 * already arranged to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
> +	SetUserIdAndSecContext(rel->rd_rel->relowner,
> +						   root_save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +
>  	relationId = RelationGetRelid(rel);
>  	namespaceId = RelationGetNamespace(rel);
>  
> @@ -422,7 +437,7 @@
>  	{
>  		AclResult	aclresult;
>  
> -		aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(),
> +		aclresult = pg_namespace_aclcheck(namespaceId, root_save_userid,
>  										  ACL_CREATE);
>  		if (aclresult != ACLCHECK_OK)
>  			aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
> @@ -449,7 +464,7 @@
>  	{
>  		AclResult	aclresult;
>  
> -		aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(),
> +		aclresult = pg_tablespace_aclcheck(tablespaceId, root_save_userid,
>  										   ACL_CREATE);
>  		if (aclresult != ACLCHECK_OK)
>  			aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
> @@ -679,15 +694,33 @@
>  
>  	if (!OidIsValid(indexRelationId))
>  	{
> +		/* Roll back any GUC changes executed by index functions. */
> +		AtEOXact_GUC(false, root_save_nestlevel);
> +
> +		/* Restore userid and security context */
> +		SetUserIdAndSecContext(root_save_userid, root_save_sec_context);
> +
>  		heap_close(rel, NoLock);
>  		return address;
>  	}
>  
> +	/*
> +	 * Roll back any GUC changes executed by index functions, and keep
> +	 * subsequent changes local to this command.  It's barely possible that
> +	 * some index function changed a behavior-affecting GUC, e.g. xmloption,
> +	 * that affects subsequent steps.
> +	 */
> +	AtEOXact_GUC(false, root_save_nestlevel);
> +	root_save_nestlevel = NewGUCNestLevel();
> +
>  	/* Add any requested comment */
>  	if (stmt->idxcomment != NULL)
>  		CreateComments(indexRelationId, RelationRelationId, 0,
>  					   stmt->idxcomment);
>  
> +	AtEOXact_GUC(false, root_save_nestlevel);
> +	SetUserIdAndSecContext(root_save_userid, root_save_sec_context);
> +
>  	if (!concurrent)
>  	{
>  		/* Close the heap and we're done, in the non-concurrent case */
> @@ -766,6 +799,16 @@
>  	/* Open and lock the parent heap relation */
>  	rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
>  
> +	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations and
> +	 * arrange to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
> +	SetUserIdAndSecContext(rel->rd_rel->relowner,
> +						   root_save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	root_save_nestlevel = NewGUCNestLevel();
> +
>  	/* And the target index relation */
>  	indexRelation = index_open(indexRelationId, RowExclusiveLock);
>  
> @@ -781,6 +824,12 @@
>  	/* Now build the index */
>  	index_build(rel, indexRelation, indexInfo, stmt->primary, false);
>  
> +	/* Roll back any GUC changes executed by index functions */
> +	AtEOXact_GUC(false, root_save_nestlevel);
> +
> +	/* Restore userid and security context */
> +	SetUserIdAndSecContext(root_save_userid, root_save_sec_context);
> +
>  	/* Close both the relations, but keep the locks */
>  	heap_close(rel, NoLock);
>  	index_close(indexRelation, NoLock);
> --- a/src/backend/utils/init/miscinit.c
> +++ b/src/backend/utils/init/miscinit.c
> @@ -365,15 +365,21 @@
>   * with guc.c's internal state, so SET ROLE has to be disallowed.
>   *
>   * SECURITY_RESTRICTED_OPERATION indicates that we are inside an operation
> - * that does not wish to trust called user-defined functions at all.  This
> - * bit prevents not only SET ROLE, but various other changes of session state
> - * that normally is unprotected but might possibly be used to subvert the
> - * calling session later.  An example is replacing an existing prepared
> - * statement with new code, which will then be executed with the outer
> - * session's permissions when the prepared statement is next used.  Since
> - * these restrictions are fairly draconian, we apply them only in contexts
> - * where the called functions are really supposed to be side-effect-free
> - * anyway, such as VACUUM/ANALYZE/REINDEX.
> + * that does not wish to trust called user-defined functions at all.  The
> + * policy is to use this before operations, e.g. autovacuum and REINDEX, that
> + * enumerate relations of a database or schema and run functions associated
> + * with each found relation.  The relation owner is the new user ID.  Set this
> + * as soon as possible after locking the relation.  Restore the old user ID as
> + * late as possible before closing the relation; restoring it shortly after
> + * close is also tolerable.  If a command has both relation-enumerating and
> + * non-enumerating modes, e.g. ANALYZE, both modes set this bit.  This bit
> + * prevents not only SET ROLE, but various other changes of session state that
> + * normally is unprotected but might possibly be used to subvert the calling
> + * session later.  An example is replacing an existing prepared statement with
> + * new code, which will then be executed with the outer session's permissions
> + * when the prepared statement is next used.  These restrictions are fairly
> + * draconian, but the functions called in relation-enumerating operations are
> + * really supposed to be side-effect-free anyway.
>   *
>   * SECURITY_NOFORCE_RLS indicates that we are inside an operation which should
>   * ignore the FORCE ROW LEVEL SECURITY per-table indication.  This is used to
> --- a/src/test/regress/expected/privileges.out
> +++ b/src/test/regress/expected/privileges.out
> @@ -1244,6 +1244,48 @@
>  -- security-restricted operations
>  \c -
>  CREATE ROLE regress_sro_user;
> +-- Check that index expressions and predicates are run as the table's owner
> +-- A dummy index function checking current_user
> +CREATE FUNCTION sro_ifun(int) RETURNS int AS $$
> +BEGIN
> +	-- Below we set the table's owner to regress_sro_user
> +	ASSERT current_user = 'regress_sro_user',
> +		format('sro_ifun(%s) called by %s', $1, current_user);
> +	RETURN $1;
> +END;
> +$$ LANGUAGE plpgsql IMMUTABLE;
> +-- Create a table owned by regress_sro_user
> +CREATE TABLE sro_tab (a int);
> +ALTER TABLE sro_tab OWNER TO regress_sro_user;
> +INSERT INTO sro_tab VALUES (1), (2), (3);
> +-- Create an expression index with a predicate
> +CREATE INDEX sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)))
> +	WHERE sro_ifun(a + 10) > sro_ifun(10);
> +DROP INDEX sro_idx;
> +-- Do the same concurrently
> +CREATE INDEX CONCURRENTLY sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)))
> +	WHERE sro_ifun(a + 10) > sro_ifun(10);
> +-- REINDEX
> +REINDEX TABLE sro_tab;
> +REINDEX INDEX sro_idx;
> +REINDEX TABLE CONCURRENTLY sro_tab;  -- v12+ feature
> +ERROR:  syntax error at or near "CONCURRENTLY"
> +LINE 1: REINDEX TABLE CONCURRENTLY sro_tab;
> +                      ^
> +DROP INDEX sro_idx;
> +-- CLUSTER
> +CREATE INDEX sro_cluster_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)));
> +CLUSTER sro_tab USING sro_cluster_idx;
> +DROP INDEX sro_cluster_idx;
> +-- BRIN index
> +CREATE INDEX sro_brin ON sro_tab USING brin ((sro_ifun(a) + sro_ifun(0)));
> +SELECT brin_summarize_new_values('sro_brin');
> + brin_summarize_new_values 
> +---------------------------
> +                         0
> +(1 row)
> +
> +DROP TABLE sro_tab;
>  SET SESSION AUTHORIZATION regress_sro_user;
>  CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS
>  	'GRANT regress_group2 TO regress_sro_user';
> --- a/src/test/regress/sql/privileges.sql
> +++ b/src/test/regress/sql/privileges.sql
> @@ -762,6 +762,42 @@
>  \c -
>  CREATE ROLE regress_sro_user;
>  
> +-- Check that index expressions and predicates are run as the table's owner
> +
> +-- A dummy index function checking current_user
> +CREATE FUNCTION sro_ifun(int) RETURNS int AS $$
> +BEGIN
> +	-- Below we set the table's owner to regress_sro_user
> +	ASSERT current_user = 'regress_sro_user',
> +		format('sro_ifun(%s) called by %s', $1, current_user);
> +	RETURN $1;
> +END;
> +$$ LANGUAGE plpgsql IMMUTABLE;
> +-- Create a table owned by regress_sro_user
> +CREATE TABLE sro_tab (a int);
> +ALTER TABLE sro_tab OWNER TO regress_sro_user;
> +INSERT INTO sro_tab VALUES (1), (2), (3);
> +-- Create an expression index with a predicate
> +CREATE INDEX sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)))
> +	WHERE sro_ifun(a + 10) > sro_ifun(10);
> +DROP INDEX sro_idx;
> +-- Do the same concurrently
> +CREATE INDEX CONCURRENTLY sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)))
> +	WHERE sro_ifun(a + 10) > sro_ifun(10);
> +-- REINDEX
> +REINDEX TABLE sro_tab;
> +REINDEX INDEX sro_idx;
> +REINDEX TABLE CONCURRENTLY sro_tab;  -- v12+ feature
> +DROP INDEX sro_idx;
> +-- CLUSTER
> +CREATE INDEX sro_cluster_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)));
> +CLUSTER sro_tab USING sro_cluster_idx;
> +DROP INDEX sro_cluster_idx;
> +-- BRIN index
> +CREATE INDEX sro_brin ON sro_tab USING brin ((sro_ifun(a) + sro_ifun(0)));
> +SELECT brin_summarize_new_values('sro_brin');
> +DROP TABLE sro_tab;
> +
>  SET SESSION AUTHORIZATION regress_sro_user;
>  CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS
>  	'GRANT regress_group2 TO regress_sro_user';
> From f26d5702857a9c027f84850af48b0eea0f3aa15c Mon Sep 17 00:00:00 2001
> From: Noah Misch <noah(at)leadboat(dot)com>
> Date: Mon, 9 May 2022 08:35:08 -0700
> Subject: [PATCH] In REFRESH MATERIALIZED VIEW, set user ID before running user
>  code.
> 
> It intended to, but did not, achieve this.  Adopt the new standard of
> setting user ID just after locking the relation.  Back-patch to v10 (all
> supported versions).
> 
> Reviewed by Simon Riggs.  Reported by Alvaro Herrera.
> 
> Security: CVE-2022-1552
> ---
>  src/backend/commands/matview.c           |   30 +++++++++++-------------------
>  src/test/regress/expected/privileges.out |   15 +++++++++++++++
>  src/test/regress/sql/privileges.sql      |   16 ++++++++++++++++
>  3 files changed, 42 insertions(+), 19 deletions(-)
> 
> --- a/src/backend/commands/matview.c
> +++ b/src/backend/commands/matview.c
> @@ -164,6 +164,17 @@
>  										  lockmode, false, false,
>  										  RangeVarCallbackOwnsTable, NULL);
>  	matviewRel = heap_open(matviewOid, NoLock);
> +	relowner = matviewRel->rd_rel->relowner;
> +
> +	/*
> +	 * Switch to the owner's userid, so that any functions are run as that
> +	 * user.  Also lock down security-restricted operations and arrange to
> +	 * make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +	SetUserIdAndSecContext(relowner,
> +						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	save_nestlevel = NewGUCNestLevel();
>  
>  	/* Make sure it is a materialized view. */
>  	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
> @@ -269,19 +280,6 @@
>  	 */
>  	SetMatViewPopulatedState(matviewRel, !stmt->skipData);
>  
> -	relowner = matviewRel->rd_rel->relowner;
> -
> -	/*
> -	 * Switch to the owner's userid, so that any functions are run as that
> -	 * user.  Also arrange to make GUC variable changes local to this command.
> -	 * Don't lock it down too tight to create a temporary table just yet.  We
> -	 * will switch modes when we are about to execute user code.
> -	 */
> -	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> -	SetUserIdAndSecContext(relowner,
> -						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
> -	save_nestlevel = NewGUCNestLevel();
> -
>  	/* Concurrent refresh builds new data in temp tablespace, and does diff. */
>  	if (concurrent)
>  	{
> @@ -304,12 +302,6 @@
>  	LockRelationOid(OIDNewHeap, AccessExclusiveLock);
>  	dest = CreateTransientRelDestReceiver(OIDNewHeap);
>  
> -	/*
> -	 * Now lock down security-restricted operations.
> -	 */
> -	SetUserIdAndSecContext(relowner,
> -						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> -
>  	/* Generate the data, if wanted. */
>  	if (!stmt->skipData)
>  		refresh_matview_datafill(dest, dataQuery, queryString);
> --- a/src/test/regress/expected/privileges.out
> +++ b/src/test/regress/expected/privileges.out
> @@ -1323,6 +1323,21 @@
>  SQL statement "SELECT unwanted_grant()"
>  PL/pgSQL function sro_trojan() line 1 at PERFORM
>  SQL function "mv_action" statement 1
> +-- REFRESH MATERIALIZED VIEW CONCURRENTLY use of eval_const_expressions()
> +SET SESSION AUTHORIZATION regress_sro_user;
> +CREATE FUNCTION unwanted_grant_nofail(int) RETURNS int
> +	IMMUTABLE LANGUAGE plpgsql AS $$
> +BEGIN
> +	PERFORM unwanted_grant();
> +	RAISE WARNING 'owned';
> +	RETURN 1;
> +EXCEPTION WHEN OTHERS THEN
> +	RETURN 2;
> +END$$;
> +CREATE MATERIALIZED VIEW sro_index_mv AS SELECT 1 AS c;
> +CREATE UNIQUE INDEX ON sro_index_mv (c) WHERE unwanted_grant_nofail(1) > 0;
> +\c -
> +REFRESH MATERIALIZED VIEW sro_index_mv;
>  DROP OWNED BY regress_sro_user;
>  DROP ROLE regress_sro_user;
>  -- Admin options
> --- a/src/test/regress/sql/privileges.sql
> +++ b/src/test/regress/sql/privileges.sql
> @@ -824,6 +824,22 @@
>  REFRESH MATERIALIZED VIEW sro_mv;
>  BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
>  
> +-- REFRESH MATERIALIZED VIEW CONCURRENTLY use of eval_const_expressions()
> +SET SESSION AUTHORIZATION regress_sro_user;
> +CREATE FUNCTION unwanted_grant_nofail(int) RETURNS int
> +	IMMUTABLE LANGUAGE plpgsql AS $$
> +BEGIN
> +	PERFORM unwanted_grant();
> +	RAISE WARNING 'owned';
> +	RETURN 1;
> +EXCEPTION WHEN OTHERS THEN
> +	RETURN 2;
> +END$$;
> +CREATE MATERIALIZED VIEW sro_index_mv AS SELECT 1 AS c;
> +CREATE UNIQUE INDEX ON sro_index_mv (c) WHERE unwanted_grant_nofail(1) > 0;
> +\c -
> +REFRESH MATERIALIZED VIEW sro_index_mv;
> +
>  DROP OWNED BY regress_sro_user;
>  DROP ROLE regress_sro_user;
>  
> From 88b39e61486a8925a3861d50c306a51eaa1af8d6 Mon Sep 17 00:00:00 2001
> From: Noah Misch <noah(at)leadboat(dot)com>
> Date: Sat, 25 Jun 2022 09:07:41 -0700
> Subject: [PATCH] CREATE INDEX: use the original userid for more ACL checks.
> 
> Commit a117cebd638dd02e5c2e791c25e43745f233111b used the original userid
> for ACL checks located directly in DefineIndex(), but it still adopted
> the table owner userid for more ACL checks than intended.  That broke
> dump/reload of indexes that refer to an operator class, collation, or
> exclusion operator in a schema other than "public" or "pg_catalog".
> Back-patch to v10 (all supported versions), like the earlier commit.
> 
> Nathan Bossart and Noah Misch
> 
> Discussion: https://postgr.es/m/f8a4105f076544c180a87ef0c4822352@stmuk.bayern.de
> ---
>  contrib/citext/Makefile                      |    2 
>  contrib/citext/expected/create_index_acl.out |   81 +++++++++++++++++++++++
>  contrib/citext/sql/create_index_acl.sql      |   82 ++++++++++++++++++++++++
>  src/backend/commands/indexcmds.c             |   92 +++++++++++++++++++++++----
>  4 files changed, 244 insertions(+), 13 deletions(-)
>  create mode 100644 contrib/citext/expected/create_index_acl.out
>  create mode 100644 contrib/citext/sql/create_index_acl.sql
> 
> --- a/contrib/citext/Makefile
> +++ b/contrib/citext/Makefile
> @@ -7,7 +7,7 @@
>  	citext--1.0--1.1.sql citext--unpackaged--1.0.sql
>  PGFILEDESC = "citext - case-insensitive character string data type"
>  
> -REGRESS = citext
> +REGRESS = create_index_acl citext
>  
>  ifdef USE_PGXS
>  PG_CONFIG = pg_config
> --- /dev/null
> +++ b/contrib/citext/expected/create_index_acl.out
> @@ -0,0 +1,81 @@
> +-- Each DefineIndex() ACL check uses either the original userid or the table
> +-- owner userid; see its header comment.  Here, confirm that DefineIndex()
> +-- uses its original userid where necessary.  The test works by creating
> +-- indexes that refer to as many sorts of objects as possible, with the table
> +-- owner having as few applicable privileges as possible.  (The privileges.sql
> +-- regress_sro_user tests look for the opposite defect; they confirm that
> +-- DefineIndex() uses the table owner userid where necessary.)
> +-- Don't override tablespaces; this version lacks allow_in_place_tablespaces.
> +BEGIN;
> +CREATE ROLE regress_minimal;
> +CREATE SCHEMA s;
> +CREATE EXTENSION citext SCHEMA s;
> +-- Revoke all conceivably-relevant ACLs within the extension.  The system
> +-- doesn't check all these ACLs, but this will provide some coverage if that
> +-- ever changes.
> +REVOKE ALL ON TYPE s.citext FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_lt(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_le(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_eq(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_ge(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_gt(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_cmp(s.citext, s.citext) FROM PUBLIC;
> +-- Functions sufficient for making an index column that has the side effect of
> +-- changing search_path at expression planning time.
> +CREATE FUNCTION public.setter() RETURNS bool VOLATILE
> +  LANGUAGE SQL AS $$SET search_path = s; SELECT true$$;
> +CREATE FUNCTION s.const() RETURNS bool IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT public.setter()$$;
> +CREATE FUNCTION s.index_this_expr(s.citext, bool) RETURNS s.citext IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT $1$$;
> +REVOKE ALL ON FUNCTION public.setter() FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.const() FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.index_this_expr(s.citext, bool) FROM PUBLIC;
> +-- Even for an empty table, expression planning calls s.const & public.setter.
> +GRANT EXECUTE ON FUNCTION public.setter() TO regress_minimal;
> +GRANT EXECUTE ON FUNCTION s.const() TO regress_minimal;
> +-- Function for index predicate.
> +CREATE FUNCTION s.index_row_if(s.citext) RETURNS bool IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT $1 IS NOT NULL$$;
> +REVOKE ALL ON FUNCTION s.index_row_if(s.citext) FROM PUBLIC;
> +-- Even for an empty table, CREATE INDEX checks ii_Predicate permissions.
> +GRANT EXECUTE ON FUNCTION s.index_row_if(s.citext) TO regress_minimal;
> +-- Non-extension, non-function objects.
> +CREATE COLLATION s.coll (LOCALE="C");
> +CREATE TABLE s.x (y s.citext);
> +ALTER TABLE s.x OWNER TO regress_minimal;
> +-- Empty-table DefineIndex()
> +CREATE UNIQUE INDEX u0rows ON s.x USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_ops)
> +  WHERE s.index_row_if(y);
> +ALTER TABLE s.x ADD CONSTRAINT e0rows EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +-- Make the table nonempty.
> +INSERT INTO s.x VALUES ('foo'), ('bar');
> +-- If the INSERT runs the planner on index expressions, a search_path change
> +-- survives.  As of 2022-06, the INSERT reuses a cached plan.  It does so even
> +-- under debug_discard_caches, since each index is new-in-transaction.  If
> +-- future work changes a cache lifecycle, this RESET may become necessary.
> +RESET search_path;
> +-- For a nonempty table, owner needs permissions throughout ii_Expressions.
> +GRANT EXECUTE ON FUNCTION s.index_this_expr(s.citext, bool) TO regress_minimal;
> +CREATE UNIQUE INDEX u2rows ON s.x USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_ops)
> +  WHERE s.index_row_if(y);
> +ALTER TABLE s.x ADD CONSTRAINT e2rows EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +-- Shall not find s.coll via search_path, despite the s.const->public.setter
> +-- call having set search_path=s during expression planning.  Suppress the
> +-- message itself, which depends on the database encoding.
> +\set VERBOSITY terse
> +DO $$
> +BEGIN
> +ALTER TABLE s.x ADD CONSTRAINT underqualified EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +EXCEPTION WHEN OTHERS THEN RAISE EXCEPTION '%', sqlstate; END$$;
> +ERROR:  42704
> +\set VERBOSITY default
> +ROLLBACK;
> --- /dev/null
> +++ b/contrib/citext/sql/create_index_acl.sql
> @@ -0,0 +1,82 @@
> +-- Each DefineIndex() ACL check uses either the original userid or the table
> +-- owner userid; see its header comment.  Here, confirm that DefineIndex()
> +-- uses its original userid where necessary.  The test works by creating
> +-- indexes that refer to as many sorts of objects as possible, with the table
> +-- owner having as few applicable privileges as possible.  (The privileges.sql
> +-- regress_sro_user tests look for the opposite defect; they confirm that
> +-- DefineIndex() uses the table owner userid where necessary.)
> +
> +-- Don't override tablespaces; this version lacks allow_in_place_tablespaces.
> +
> +BEGIN;
> +CREATE ROLE regress_minimal;
> +CREATE SCHEMA s;
> +CREATE EXTENSION citext SCHEMA s;
> +-- Revoke all conceivably-relevant ACLs within the extension.  The system
> +-- doesn't check all these ACLs, but this will provide some coverage if that
> +-- ever changes.
> +REVOKE ALL ON TYPE s.citext FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_lt(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_le(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_eq(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_ge(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_gt(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_cmp(s.citext, s.citext) FROM PUBLIC;
> +-- Functions sufficient for making an index column that has the side effect of
> +-- changing search_path at expression planning time.
> +CREATE FUNCTION public.setter() RETURNS bool VOLATILE
> +  LANGUAGE SQL AS $$SET search_path = s; SELECT true$$;
> +CREATE FUNCTION s.const() RETURNS bool IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT public.setter()$$;
> +CREATE FUNCTION s.index_this_expr(s.citext, bool) RETURNS s.citext IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT $1$$;
> +REVOKE ALL ON FUNCTION public.setter() FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.const() FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.index_this_expr(s.citext, bool) FROM PUBLIC;
> +-- Even for an empty table, expression planning calls s.const & public.setter.
> +GRANT EXECUTE ON FUNCTION public.setter() TO regress_minimal;
> +GRANT EXECUTE ON FUNCTION s.const() TO regress_minimal;
> +-- Function for index predicate.
> +CREATE FUNCTION s.index_row_if(s.citext) RETURNS bool IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT $1 IS NOT NULL$$;
> +REVOKE ALL ON FUNCTION s.index_row_if(s.citext) FROM PUBLIC;
> +-- Even for an empty table, CREATE INDEX checks ii_Predicate permissions.
> +GRANT EXECUTE ON FUNCTION s.index_row_if(s.citext) TO regress_minimal;
> +-- Non-extension, non-function objects.
> +CREATE COLLATION s.coll (LOCALE="C");
> +CREATE TABLE s.x (y s.citext);
> +ALTER TABLE s.x OWNER TO regress_minimal;
> +-- Empty-table DefineIndex()
> +CREATE UNIQUE INDEX u0rows ON s.x USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_ops)
> +  WHERE s.index_row_if(y);
> +ALTER TABLE s.x ADD CONSTRAINT e0rows EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +-- Make the table nonempty.
> +INSERT INTO s.x VALUES ('foo'), ('bar');
> +-- If the INSERT runs the planner on index expressions, a search_path change
> +-- survives.  As of 2022-06, the INSERT reuses a cached plan.  It does so even
> +-- under debug_discard_caches, since each index is new-in-transaction.  If
> +-- future work changes a cache lifecycle, this RESET may become necessary.
> +RESET search_path;
> +-- For a nonempty table, owner needs permissions throughout ii_Expressions.
> +GRANT EXECUTE ON FUNCTION s.index_this_expr(s.citext, bool) TO regress_minimal;
> +CREATE UNIQUE INDEX u2rows ON s.x USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_ops)
> +  WHERE s.index_row_if(y);
> +ALTER TABLE s.x ADD CONSTRAINT e2rows EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +-- Shall not find s.coll via search_path, despite the s.const->public.setter
> +-- call having set search_path=s during expression planning.  Suppress the
> +-- message itself, which depends on the database encoding.
> +\set VERBOSITY terse
> +DO $$
> +BEGIN
> +ALTER TABLE s.x ADD CONSTRAINT underqualified EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +EXCEPTION WHEN OTHERS THEN RAISE EXCEPTION '%', sqlstate; END$$;
> +\set VERBOSITY default
> +ROLLBACK;
> --- a/src/backend/commands/indexcmds.c
> +++ b/src/backend/commands/indexcmds.c
> @@ -70,7 +70,10 @@
>  				  Oid relId,
>  				  char *accessMethodName, Oid accessMethodId,
>  				  bool amcanorder,
> -				  bool isconstraint);
> +				  bool isconstraint,
> +				  Oid ddl_userid,
> +				  int ddl_sec_context,
> +				  int *ddl_save_nestlevel);
>  static Oid GetIndexOpClass(List *opclass, Oid attrType,
>  				char *accessMethodName, Oid accessMethodId);
>  static char *ChooseIndexName(const char *tabname, Oid namespaceId,
> @@ -176,8 +179,7 @@
>  	 * Compute the operator classes, collations, and exclusion operators for
>  	 * the new index, so we can test whether it's compatible with the existing
>  	 * one.  Note that ComputeIndexAttrs might fail here, but that's OK:
> -	 * DefineIndex would have called this function with the same arguments
> -	 * later on, and it would have failed then anyway.
> +	 * DefineIndex would have failed later.
>  	 */
>  	indexInfo = makeNode(IndexInfo);
>  	indexInfo->ii_Expressions = NIL;
> @@ -195,7 +197,7 @@
>  					  coloptions, attributeList,
>  					  exclusionOpNames, relationId,
>  					  accessMethodName, accessMethodId,
> -					  amcanorder, isconstraint);
> +					  amcanorder, isconstraint, InvalidOid, 0, NULL);
>  
>  
>  	/* Get the soon-obsolete pg_index tuple. */
> @@ -288,6 +290,19 @@
>   * DefineIndex
>   *		Creates a new index.
>   *
> + * This function manages the current userid according to the needs of pg_dump.
> + * Recreating old-database catalog entries in new-database is fine, regardless
> + * of which users would have permission to recreate those entries now.  That's
> + * just preservation of state.  Running opaque expressions, like calling a
> + * function named in a catalog entry or evaluating a pg_node_tree in a catalog
> + * entry, as anyone other than the object owner, is not fine.  To adhere to
> + * those principles and to remain fail-safe, use the table owner userid for
> + * most ACL checks.  Use the original userid for ACL checks reached without
> + * traversing opaque expressions.  (pg_dump can predict such ACL checks from
> + * catalogs.)  Overall, this is a mess.  Future DDL development should
> + * consider offering one DDL command for catalog setup and a separate DDL
> + * command for steps that run opaque expressions.
> + *
>   * 'relationId': the OID of the heap relation on which the index is to be
>   *		created
>   * 'stmt': IndexStmt describing the properties of the new index.
> @@ -598,7 +613,8 @@
>  					  coloptions, stmt->indexParams,
>  					  stmt->excludeOpNames, relationId,
>  					  accessMethodName, accessMethodId,
> -					  amcanorder, stmt->isconstraint);
> +					  amcanorder, stmt->isconstraint, root_save_userid,
> +					  root_save_sec_context, &root_save_nestlevel);
>  
>  	/*
>  	 * Extra checks when creating a PRIMARY KEY index.
> @@ -706,9 +722,8 @@
>  
>  	/*
>  	 * Roll back any GUC changes executed by index functions, and keep
> -	 * subsequent changes local to this command.  It's barely possible that
> -	 * some index function changed a behavior-affecting GUC, e.g. xmloption,
> -	 * that affects subsequent steps.
> +	 * subsequent changes local to this command.  This is essential if some
> +	 * index function changed a behavior-affecting GUC, e.g. search_path.
>  	 */
>  	AtEOXact_GUC(false, root_save_nestlevel);
>  	root_save_nestlevel = NewGUCNestLevel();
> @@ -1063,6 +1078,10 @@
>  /*
>   * Compute per-index-column information, including indexed column numbers
>   * or index expressions, opclasses, and indoptions.
> + *
> + * If the caller switched to the table owner, ddl_userid is the role for ACL
> + * checks reached without traversing opaque expressions.  Otherwise, it's
> + * InvalidOid, and other ddl_* arguments are undefined.
>   */
>  static void
>  ComputeIndexAttrs(IndexInfo *indexInfo,
> @@ -1076,11 +1095,16 @@
>  				  char *accessMethodName,
>  				  Oid accessMethodId,
>  				  bool amcanorder,
> -				  bool isconstraint)
> +				  bool isconstraint,
> +				  Oid ddl_userid,
> +				  int ddl_sec_context,
> +				  int *ddl_save_nestlevel)
>  {
>  	ListCell   *nextExclOp;
>  	ListCell   *lc;
>  	int			attn;
> +	Oid			save_userid;
> +	int			save_sec_context;
>  
>  	/* Allocate space for exclusion operator info, if needed */
>  	if (exclusionOpNames)
> @@ -1096,6 +1120,9 @@
>  	else
>  		nextExclOp = NULL;
>  
> +	if (OidIsValid(ddl_userid))
> +		GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +
>  	/*
>  	 * process attributeList
>  	 */
> @@ -1190,10 +1217,24 @@
>  		typeOidP[attn] = atttype;
>  
>  		/*
> -		 * Apply collation override if any
> +		 * Apply collation override if any.  Use of ddl_userid is necessary
> +		 * due to ACL checks therein, and it's safe because collations don't
> +		 * contain opaque expressions (or non-opaque expressions).
>  		 */
>  		if (attribute->collation)
> +		{
> +			if (OidIsValid(ddl_userid))
> +			{
> +				AtEOXact_GUC(false, *ddl_save_nestlevel);
> +				SetUserIdAndSecContext(ddl_userid, ddl_sec_context);
> +			}
>  			attcollation = get_collation_oid(attribute->collation, false);
> +			if (OidIsValid(ddl_userid))
> +			{
> +				SetUserIdAndSecContext(save_userid, save_sec_context);
> +				*ddl_save_nestlevel = NewGUCNestLevel();
> +			}
> +		}
>  
>  		/*
>  		 * Check we have a collation iff it's a collatable type.  The only
> @@ -1221,12 +1262,25 @@
>  		collationOidP[attn] = attcollation;
>  
>  		/*
> -		 * Identify the opclass to use.
> +		 * Identify the opclass to use.  Use of ddl_userid is necessary due to
> +		 * ACL checks therein.  This is safe despite opclasses containing
> +		 * opaque expressions (specifically, functions), because only
> +		 * superusers can define opclasses.
>  		 */
> +		if (OidIsValid(ddl_userid))
> +		{
> +			AtEOXact_GUC(false, *ddl_save_nestlevel);
> +			SetUserIdAndSecContext(ddl_userid, ddl_sec_context);
> +		}
>  		classOidP[attn] = GetIndexOpClass(attribute->opclass,
>  										  atttype,
>  										  accessMethodName,
>  										  accessMethodId);
> +		if (OidIsValid(ddl_userid))
> +		{
> +			SetUserIdAndSecContext(save_userid, save_sec_context);
> +			*ddl_save_nestlevel = NewGUCNestLevel();
> +		}
>  
>  		/*
>  		 * Identify the exclusion operator, if any.
> @@ -1240,9 +1294,23 @@
>  
>  			/*
>  			 * Find the operator --- it must accept the column datatype
> -			 * without runtime coercion (but binary compatibility is OK)
> +			 * without runtime coercion (but binary compatibility is OK).
> +			 * Operators contain opaque expressions (specifically, functions).
> +			 * compatible_oper_opid() boils down to oper() and
> +			 * IsBinaryCoercible().  PostgreSQL would have security problems
> +			 * elsewhere if oper() started calling opaque expressions.
>  			 */
> +			if (OidIsValid(ddl_userid))
> +			{
> +				AtEOXact_GUC(false, *ddl_save_nestlevel);
> +				SetUserIdAndSecContext(ddl_userid, ddl_sec_context);
> +			}
>  			opid = compatible_oper_opid(opname, atttype, atttype, false);
> +			if (OidIsValid(ddl_userid))
> +			{
> +				SetUserIdAndSecContext(save_userid, save_sec_context);
> +				*ddl_save_nestlevel = NewGUCNestLevel();
> +			}
>  
>  			/*
>  			 * Only allow commutative operators to be used in exclusion
> From ef792f7856dea2576dcd9cab92b2b05fe955696b Mon Sep 17 00:00:00 2001
> From: Noah Misch <noah(at)leadboat(dot)com>
> Date: Mon, 9 May 2022 08:35:08 -0700
> Subject: [PATCH] Make relation-enumerating operations be security-restricted
>  operations.
> 
> When a feature enumerates relations and runs functions associated with
> all found relations, the feature's user shall not need to trust every
> user having permission to create objects.  BRIN-specific functionality
> in autovacuum neglected to account for this, as did pg_amcheck and
> CLUSTER.  An attacker having permission to create non-temp objects in at
> least one schema could execute arbitrary SQL functions under the
> identity of the bootstrap superuser.  CREATE INDEX (not a
> relation-enumerating operation) and REINDEX protected themselves too
> late.  This change extends to the non-enumerating amcheck interface.
> Back-patch to v10 (all supported versions).
> 
> Sergey Shinderuk, reviewed (in earlier versions) by Alexander Lakhin.
> Reported by Alexander Lakhin.
> 
> Security: CVE-2022-1552
> ---
>  src/backend/catalog/index.c              |   41 +++++++++++++++++++--------
>  src/backend/commands/cluster.c           |   35 ++++++++++++++++++-----
>  src/backend/commands/indexcmds.c         |   47 +++++++++++++++++++++++++++++--
>  src/backend/utils/init/miscinit.c        |   24 +++++++++------
>  src/test/regress/expected/privileges.out |   35 +++++++++++++++++++++++
>  src/test/regress/sql/privileges.sql      |   34 ++++++++++++++++++++++
>  6 files changed, 187 insertions(+), 29 deletions(-)
> 
> --- a/src/backend/catalog/index.c
> +++ b/src/backend/catalog/index.c
> @@ -2743,7 +2743,17 @@
>  
>  	/* Open and lock the parent heap relation */
>  	heapRelation = heap_open(heapId, ShareUpdateExclusiveLock);
> -	/* And the target index relation */
> +
> +	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations and
> +	 * arrange to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
> +						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	save_nestlevel = NewGUCNestLevel();
> +
>  	indexRelation = index_open(indexId, RowExclusiveLock);
>  
>  	/*
> @@ -2757,16 +2767,6 @@
>  	indexInfo->ii_Concurrent = true;
>  
>  	/*
> -	 * Switch to the table owner's userid, so that any index functions are run
> -	 * as that user.  Also lock down security-restricted operations and
> -	 * arrange to make GUC variable changes local to this command.
> -	 */
> -	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> -	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
> -						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> -	save_nestlevel = NewGUCNestLevel();
> -
> -	/*
>  	 * Scan the index and gather up all the TIDs into a tuplesort object.
>  	 */
>  	ivinfo.index = indexRelation;
> @@ -3178,6 +3178,9 @@
>  	Relation	iRel,
>  				heapRelation;
>  	Oid			heapId;
> +	Oid			save_userid;
> +	int			save_sec_context;
> +	int			save_nestlevel;
>  	IndexInfo  *indexInfo;
>  	volatile bool skipped_constraint = false;
>  
> @@ -3189,6 +3192,16 @@
>  	heapRelation = heap_open(heapId, ShareLock);
>  
>  	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations and
> +	 * arrange to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +	SetUserIdAndSecContext(heapRelation->rd_rel->relowner,
> +						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	save_nestlevel = NewGUCNestLevel();
> +
> +	/*
>  	 * Open the target index relation and get an exclusive lock on it, to
>  	 * ensure that no one else is touching this particular index.
>  	 */
> @@ -3324,6 +3337,12 @@
>  		heap_close(pg_index, RowExclusiveLock);
>  	}
>  
> +	/* Roll back any GUC changes executed by index functions */
> +	AtEOXact_GUC(false, save_nestlevel);
> +
> +	/* Restore userid and security context */
> +	SetUserIdAndSecContext(save_userid, save_sec_context);
> +
>  	/* Close rels, but keep locks */
>  	index_close(iRel, NoLock);
>  	heap_close(heapRelation, NoLock);
> --- a/src/backend/commands/cluster.c
> +++ b/src/backend/commands/cluster.c
> @@ -41,6 +41,7 @@
>  #include "storage/smgr.h"
>  #include "utils/acl.h"
>  #include "utils/fmgroids.h"
> +#include "utils/guc.h"
>  #include "utils/inval.h"
>  #include "utils/lsyscache.h"
>  #include "utils/memutils.h"
> @@ -259,6 +260,9 @@
>  cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
>  {
>  	Relation	OldHeap;
> +	Oid			save_userid;
> +	int			save_sec_context;
> +	int			save_nestlevel;
>  
>  	/* Check for user-requested abort. */
>  	CHECK_FOR_INTERRUPTS();
> @@ -276,6 +280,16 @@
>  		return;
>  
>  	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations and
> +	 * arrange to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +	SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
> +						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	save_nestlevel = NewGUCNestLevel();
> +
> +	/*
>  	 * Since we may open a new transaction for each relation, we have to check
>  	 * that the relation still is what we think it is.
>  	 *
> @@ -289,10 +303,10 @@
>  		Form_pg_index indexForm;
>  
>  		/* Check that the user still owns the relation */
> -		if (!pg_class_ownercheck(tableOid, GetUserId()))
> +		if (!pg_class_ownercheck(tableOid, save_userid))
>  		{
>  			relation_close(OldHeap, AccessExclusiveLock);
> -			return;
> +			goto out;
>  		}
>  
>  		/*
> @@ -306,7 +320,7 @@
>  		if (RELATION_IS_OTHER_TEMP(OldHeap))
>  		{
>  			relation_close(OldHeap, AccessExclusiveLock);
> -			return;
> +			goto out;
>  		}
>  
>  		if (OidIsValid(indexOid))
> @@ -317,7 +331,7 @@
>  			if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
>  			{
>  				relation_close(OldHeap, AccessExclusiveLock);
> -				return;
> +				goto out;
>  			}
>  
>  			/*
> @@ -327,14 +341,14 @@
>  			if (!HeapTupleIsValid(tuple))		/* probably can't happen */
>  			{
>  				relation_close(OldHeap, AccessExclusiveLock);
> -				return;
> +				goto out;
>  			}
>  			indexForm = (Form_pg_index) GETSTRUCT(tuple);
>  			if (!indexForm->indisclustered)
>  			{
>  				ReleaseSysCache(tuple);
>  				relation_close(OldHeap, AccessExclusiveLock);
> -				return;
> +				goto out;
>  			}
>  			ReleaseSysCache(tuple);
>  		}
> @@ -388,7 +402,7 @@
>  		!RelationIsPopulated(OldHeap))
>  	{
>  		relation_close(OldHeap, AccessExclusiveLock);
> -		return;
> +		goto out;
>  	}
>  
>  	/*
> @@ -403,6 +417,13 @@
>  	rebuild_relation(OldHeap, indexOid, verbose);
>  
>  	/* NB: rebuild_relation does heap_close() on OldHeap */
> +
> +out:
> +	/* Roll back any GUC changes executed by index functions */
> +	AtEOXact_GUC(false, save_nestlevel);
> +
> +	/* Restore userid and security context */
> +	SetUserIdAndSecContext(save_userid, save_sec_context);
>  }
>  
>  /*
> --- a/src/backend/commands/indexcmds.c
> +++ b/src/backend/commands/indexcmds.c
> @@ -44,6 +44,7 @@
>  #include "utils/acl.h"
>  #include "utils/builtins.h"
>  #include "utils/fmgroids.h"
> +#include "utils/guc.h"
>  #include "utils/inval.h"
>  #include "utils/lsyscache.h"
>  #include "utils/memutils.h"
> @@ -329,8 +330,13 @@
>  	LOCKTAG		heaplocktag;
>  	LOCKMODE	lockmode;
>  	Snapshot	snapshot;
> +	Oid			root_save_userid;
> +	int			root_save_sec_context;
> +	int			root_save_nestlevel;
>  	int			i;
>  
> +	root_save_nestlevel = NewGUCNestLevel();
> +
>  	/*
>  	 * Force non-concurrent build on temporary relations, even if CONCURRENTLY
>  	 * was requested.  Other backends can't access a temporary relation, so
> @@ -371,6 +377,15 @@
>  	lockmode = concurrent ? ShareUpdateExclusiveLock : ShareLock;
>  	rel = heap_open(relationId, lockmode);
>  
> +	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations.  We
> +	 * already arranged to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
> +	SetUserIdAndSecContext(rel->rd_rel->relowner,
> +						   root_save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +
>  	relationId = RelationGetRelid(rel);
>  	namespaceId = RelationGetNamespace(rel);
>  
> @@ -412,7 +427,7 @@
>  	{
>  		AclResult	aclresult;
>  
> -		aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(),
> +		aclresult = pg_namespace_aclcheck(namespaceId, root_save_userid,
>  										  ACL_CREATE);
>  		if (aclresult != ACLCHECK_OK)
>  			aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
> @@ -439,7 +454,7 @@
>  	{
>  		AclResult	aclresult;
>  
> -		aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(),
> +		aclresult = pg_tablespace_aclcheck(tablespaceId, root_save_userid,
>  										   ACL_CREATE);
>  		if (aclresult != ACLCHECK_OK)
>  			aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
> @@ -622,11 +637,23 @@
>  					 skip_build || concurrent,
>  					 concurrent, !check_rights);
>  
> +	/*
> +	 * Roll back any GUC changes executed by index functions, and keep
> +	 * subsequent changes local to this command.  It's barely possible that
> +	 * some index function changed a behavior-affecting GUC, e.g. xmloption,
> +	 * that affects subsequent steps.
> +	 */
> +	AtEOXact_GUC(false, root_save_nestlevel);
> +	root_save_nestlevel = NewGUCNestLevel();
> +
>  	/* Add any requested comment */
>  	if (stmt->idxcomment != NULL)
>  		CreateComments(indexRelationId, RelationRelationId, 0,
>  					   stmt->idxcomment);
>  
> +	AtEOXact_GUC(false, root_save_nestlevel);
> +	SetUserIdAndSecContext(root_save_userid, root_save_sec_context);
> +
>  	if (!concurrent)
>  	{
>  		/* Close the heap and we're done, in the non-concurrent case */
> @@ -705,6 +732,16 @@
>  	/* Open and lock the parent heap relation */
>  	rel = heap_openrv(stmt->relation, ShareUpdateExclusiveLock);
>  
> +	/*
> +	 * Switch to the table owner's userid, so that any index functions are run
> +	 * as that user.  Also lock down security-restricted operations and
> +	 * arrange to make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&root_save_userid, &root_save_sec_context);
> +	SetUserIdAndSecContext(rel->rd_rel->relowner,
> +						   root_save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	root_save_nestlevel = NewGUCNestLevel();
> +
>  	/* And the target index relation */
>  	indexRelation = index_open(indexRelationId, RowExclusiveLock);
>  
> @@ -720,6 +757,12 @@
>  	/* Now build the index */
>  	index_build(rel, indexRelation, indexInfo, stmt->primary, false);
>  
> +	/* Roll back any GUC changes executed by index functions */
> +	AtEOXact_GUC(false, root_save_nestlevel);
> +
> +	/* Restore userid and security context */
> +	SetUserIdAndSecContext(root_save_userid, root_save_sec_context);
> +
>  	/* Close both the relations, but keep the locks */
>  	heap_close(rel, NoLock);
>  	index_close(indexRelation, NoLock);
> --- a/src/backend/utils/init/miscinit.c
> +++ b/src/backend/utils/init/miscinit.c
> @@ -235,15 +235,21 @@
>   * with guc.c's internal state, so SET ROLE has to be disallowed.
>   *
>   * SECURITY_RESTRICTED_OPERATION indicates that we are inside an operation
> - * that does not wish to trust called user-defined functions at all.  This
> - * bit prevents not only SET ROLE, but various other changes of session state
> - * that normally is unprotected but might possibly be used to subvert the
> - * calling session later.  An example is replacing an existing prepared
> - * statement with new code, which will then be executed with the outer
> - * session's permissions when the prepared statement is next used.  Since
> - * these restrictions are fairly draconian, we apply them only in contexts
> - * where the called functions are really supposed to be side-effect-free
> - * anyway, such as VACUUM/ANALYZE/REINDEX.
> + * that does not wish to trust called user-defined functions at all.  The
> + * policy is to use this before operations, e.g. autovacuum and REINDEX, that
> + * enumerate relations of a database or schema and run functions associated
> + * with each found relation.  The relation owner is the new user ID.  Set this
> + * as soon as possible after locking the relation.  Restore the old user ID as
> + * late as possible before closing the relation; restoring it shortly after
> + * close is also tolerable.  If a command has both relation-enumerating and
> + * non-enumerating modes, e.g. ANALYZE, both modes set this bit.  This bit
> + * prevents not only SET ROLE, but various other changes of session state that
> + * normally is unprotected but might possibly be used to subvert the calling
> + * session later.  An example is replacing an existing prepared statement with
> + * new code, which will then be executed with the outer session's permissions
> + * when the prepared statement is next used.  These restrictions are fairly
> + * draconian, but the functions called in relation-enumerating operations are
> + * really supposed to be side-effect-free anyway.
>   *
>   * Unlike GetUserId, GetUserIdAndSecContext does *not* Assert that the current
>   * value of CurrentUserId is valid; nor does SetUserIdAndSecContext require
> --- a/src/test/regress/expected/privileges.out
> +++ b/src/test/regress/expected/privileges.out
> @@ -1194,6 +1194,41 @@
>  -- security-restricted operations
>  \c -
>  CREATE ROLE regress_sro_user;
> +-- Check that index expressions and predicates are run as the table's owner
> +-- A dummy index function checking current_user
> +CREATE FUNCTION sro_ifun(int) RETURNS int AS $$
> +BEGIN
> +	-- Below we set the table's owner to regress_sro_user
> +	IF current_user <> 'regress_sro_user' THEN
> +		RAISE 'called by %', current_user;
> +	END IF;
> +	RETURN $1;
> +END;
> +$$ LANGUAGE plpgsql IMMUTABLE;
> +-- Create a table owned by regress_sro_user
> +CREATE TABLE sro_tab (a int);
> +ALTER TABLE sro_tab OWNER TO regress_sro_user;
> +INSERT INTO sro_tab VALUES (1), (2), (3);
> +-- Create an expression index with a predicate
> +CREATE INDEX sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)))
> +	WHERE sro_ifun(a + 10) > sro_ifun(10);
> +DROP INDEX sro_idx;
> +-- Do the same concurrently
> +CREATE INDEX CONCURRENTLY sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)))
> +	WHERE sro_ifun(a + 10) > sro_ifun(10);
> +-- REINDEX
> +REINDEX TABLE sro_tab;
> +REINDEX INDEX sro_idx;
> +REINDEX TABLE CONCURRENTLY sro_tab;  -- v12+ feature
> +ERROR:  syntax error at or near "CONCURRENTLY"
> +LINE 1: REINDEX TABLE CONCURRENTLY sro_tab;
> +                      ^
> +DROP INDEX sro_idx;
> +-- CLUSTER
> +CREATE INDEX sro_cluster_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)));
> +CLUSTER sro_tab USING sro_cluster_idx;
> +DROP INDEX sro_cluster_idx;
> +DROP TABLE sro_tab;
>  SET SESSION AUTHORIZATION regress_sro_user;
>  CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS
>  	'GRANT regressgroup2 TO regress_sro_user';
> --- a/src/test/regress/sql/privileges.sql
> +++ b/src/test/regress/sql/privileges.sql
> @@ -724,6 +724,40 @@
>  \c -
>  CREATE ROLE regress_sro_user;
>  
> +-- Check that index expressions and predicates are run as the table's owner
> +
> +-- A dummy index function checking current_user
> +CREATE FUNCTION sro_ifun(int) RETURNS int AS $$
> +BEGIN
> +	-- Below we set the table's owner to regress_sro_user
> +	IF current_user <> 'regress_sro_user' THEN
> +		RAISE 'called by %', current_user;
> +	END IF;
> +	RETURN $1;
> +END;
> +$$ LANGUAGE plpgsql IMMUTABLE;
> +-- Create a table owned by regress_sro_user
> +CREATE TABLE sro_tab (a int);
> +ALTER TABLE sro_tab OWNER TO regress_sro_user;
> +INSERT INTO sro_tab VALUES (1), (2), (3);
> +-- Create an expression index with a predicate
> +CREATE INDEX sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)))
> +	WHERE sro_ifun(a + 10) > sro_ifun(10);
> +DROP INDEX sro_idx;
> +-- Do the same concurrently
> +CREATE INDEX CONCURRENTLY sro_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)))
> +	WHERE sro_ifun(a + 10) > sro_ifun(10);
> +-- REINDEX
> +REINDEX TABLE sro_tab;
> +REINDEX INDEX sro_idx;
> +REINDEX TABLE CONCURRENTLY sro_tab;  -- v12+ feature
> +DROP INDEX sro_idx;
> +-- CLUSTER
> +CREATE INDEX sro_cluster_idx ON sro_tab ((sro_ifun(a) + sro_ifun(0)));
> +CLUSTER sro_tab USING sro_cluster_idx;
> +DROP INDEX sro_cluster_idx;
> +DROP TABLE sro_tab;
> +
>  SET SESSION AUTHORIZATION regress_sro_user;
>  CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS
>  	'GRANT regressgroup2 TO regress_sro_user';
> From f26d5702857a9c027f84850af48b0eea0f3aa15c Mon Sep 17 00:00:00 2001
> From: Noah Misch <noah(at)leadboat(dot)com>
> Date: Mon, 9 May 2022 08:35:08 -0700
> Subject: [PATCH] In REFRESH MATERIALIZED VIEW, set user ID before running user
>  code.
> 
> It intended to, but did not, achieve this.  Adopt the new standard of
> setting user ID just after locking the relation.  Back-patch to v10 (all
> supported versions).
> 
> Reviewed by Simon Riggs.  Reported by Alvaro Herrera.
> 
> Security: CVE-2022-1552
> ---
>  src/backend/commands/matview.c           |   30 +++++++++++-------------------
>  src/test/regress/expected/privileges.out |   15 +++++++++++++++
>  src/test/regress/sql/privileges.sql      |   16 ++++++++++++++++
>  3 files changed, 42 insertions(+), 19 deletions(-)
> 
> --- a/src/backend/commands/matview.c
> +++ b/src/backend/commands/matview.c
> @@ -161,6 +161,17 @@
>  										  lockmode, false, false,
>  										  RangeVarCallbackOwnsTable, NULL);
>  	matviewRel = heap_open(matviewOid, NoLock);
> +	relowner = matviewRel->rd_rel->relowner;
> +
> +	/*
> +	 * Switch to the owner's userid, so that any functions are run as that
> +	 * user.  Also lock down security-restricted operations and arrange to
> +	 * make GUC variable changes local to this command.
> +	 */
> +	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +	SetUserIdAndSecContext(relowner,
> +						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> +	save_nestlevel = NewGUCNestLevel();
>  
>  	/* Make sure it is a materialized view. */
>  	if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW)
> @@ -233,19 +244,6 @@
>  	 */
>  	SetMatViewPopulatedState(matviewRel, !stmt->skipData);
>  
> -	relowner = matviewRel->rd_rel->relowner;
> -
> -	/*
> -	 * Switch to the owner's userid, so that any functions are run as that
> -	 * user.  Also arrange to make GUC variable changes local to this command.
> -	 * Don't lock it down too tight to create a temporary table just yet.  We
> -	 * will switch modes when we are about to execute user code.
> -	 */
> -	GetUserIdAndSecContext(&save_userid, &save_sec_context);
> -	SetUserIdAndSecContext(relowner,
> -						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
> -	save_nestlevel = NewGUCNestLevel();
> -
>  	/* Concurrent refresh builds new data in temp tablespace, and does diff. */
>  	if (concurrent)
>  		tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP);
> @@ -262,12 +260,6 @@
>  	LockRelationOid(OIDNewHeap, AccessExclusiveLock);
>  	dest = CreateTransientRelDestReceiver(OIDNewHeap);
>  
> -	/*
> -	 * Now lock down security-restricted operations.
> -	 */
> -	SetUserIdAndSecContext(relowner,
> -						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
> -
>  	/* Generate the data, if wanted. */
>  	if (!stmt->skipData)
>  		refresh_matview_datafill(dest, dataQuery, queryString);
> --- a/src/test/regress/expected/privileges.out
> +++ b/src/test/regress/expected/privileges.out
> @@ -1266,6 +1266,21 @@
>  SQL statement "SELECT unwanted_grant()"
>  PL/pgSQL function sro_trojan() line 1 at PERFORM
>  SQL function "mv_action" statement 1
> +-- REFRESH MATERIALIZED VIEW CONCURRENTLY use of eval_const_expressions()
> +SET SESSION AUTHORIZATION regress_sro_user;
> +CREATE FUNCTION unwanted_grant_nofail(int) RETURNS int
> +	IMMUTABLE LANGUAGE plpgsql AS $$
> +BEGIN
> +	PERFORM unwanted_grant();
> +	RAISE WARNING 'owned';
> +	RETURN 1;
> +EXCEPTION WHEN OTHERS THEN
> +	RETURN 2;
> +END$$;
> +CREATE MATERIALIZED VIEW sro_index_mv AS SELECT 1 AS c;
> +CREATE UNIQUE INDEX ON sro_index_mv (c) WHERE unwanted_grant_nofail(1) > 0;
> +\c -
> +REFRESH MATERIALIZED VIEW sro_index_mv;
>  DROP OWNED BY regress_sro_user;
>  DROP ROLE regress_sro_user;
>  -- Admin options
> --- a/src/test/regress/sql/privileges.sql
> +++ b/src/test/regress/sql/privileges.sql
> @@ -784,6 +784,22 @@
>  REFRESH MATERIALIZED VIEW sro_mv;
>  BEGIN; SET CONSTRAINTS ALL IMMEDIATE; REFRESH MATERIALIZED VIEW sro_mv; COMMIT;
>  
> +-- REFRESH MATERIALIZED VIEW CONCURRENTLY use of eval_const_expressions()
> +SET SESSION AUTHORIZATION regress_sro_user;
> +CREATE FUNCTION unwanted_grant_nofail(int) RETURNS int
> +	IMMUTABLE LANGUAGE plpgsql AS $$
> +BEGIN
> +	PERFORM unwanted_grant();
> +	RAISE WARNING 'owned';
> +	RETURN 1;
> +EXCEPTION WHEN OTHERS THEN
> +	RETURN 2;
> +END$$;
> +CREATE MATERIALIZED VIEW sro_index_mv AS SELECT 1 AS c;
> +CREATE UNIQUE INDEX ON sro_index_mv (c) WHERE unwanted_grant_nofail(1) > 0;
> +\c -
> +REFRESH MATERIALIZED VIEW sro_index_mv;
> +
>  DROP OWNED BY regress_sro_user;
>  DROP ROLE regress_sro_user;
>  
> From 88b39e61486a8925a3861d50c306a51eaa1af8d6 Mon Sep 17 00:00:00 2001
> From: Noah Misch <noah(at)leadboat(dot)com>
> Date: Sat, 25 Jun 2022 09:07:41 -0700
> Subject: [PATCH] CREATE INDEX: use the original userid for more ACL checks.
> 
> Commit a117cebd638dd02e5c2e791c25e43745f233111b used the original userid
> for ACL checks located directly in DefineIndex(), but it still adopted
> the table owner userid for more ACL checks than intended.  That broke
> dump/reload of indexes that refer to an operator class, collation, or
> exclusion operator in a schema other than "public" or "pg_catalog".
> Back-patch to v10 (all supported versions), like the earlier commit.
> 
> Nathan Bossart and Noah Misch
> 
> Discussion: https://postgr.es/m/f8a4105f076544c180a87ef0c4822352@stmuk.bayern.de
> ---
>  contrib/citext/Makefile                      |    2 
>  contrib/citext/expected/create_index_acl.out |   81 +++++++++++++++++++++++
>  contrib/citext/sql/create_index_acl.sql      |   82 ++++++++++++++++++++++++
>  src/backend/commands/indexcmds.c             |   92 +++++++++++++++++++++++----
>  4 files changed, 244 insertions(+), 13 deletions(-)
>  create mode 100644 contrib/citext/expected/create_index_acl.out
>  create mode 100644 contrib/citext/sql/create_index_acl.sql
> 
> --- a/contrib/citext/Makefile
> +++ b/contrib/citext/Makefile
> @@ -7,7 +7,7 @@
>         citext--1.1--1.0.sql citext--unpackaged--1.0.sql
>  PGFILEDESC = "citext - case-insensitive character string data type"
>  
> -REGRESS = citext
> +REGRESS = create_index_acl citext
>  
>  ifdef USE_PGXS
>  PG_CONFIG = pg_config
> --- /dev/null
> +++ b/contrib/citext/expected/create_index_acl.out
> @@ -0,0 +1,81 @@
> +-- Each DefineIndex() ACL check uses either the original userid or the table
> +-- owner userid; see its header comment.  Here, confirm that DefineIndex()
> +-- uses its original userid where necessary.  The test works by creating
> +-- indexes that refer to as many sorts of objects as possible, with the table
> +-- owner having as few applicable privileges as possible.  (The privileges.sql
> +-- regress_sro_user tests look for the opposite defect; they confirm that
> +-- DefineIndex() uses the table owner userid where necessary.)
> +-- Don't override tablespaces; this version lacks allow_in_place_tablespaces.
> +BEGIN;
> +CREATE ROLE regress_minimal;
> +CREATE SCHEMA s;
> +CREATE EXTENSION citext SCHEMA s;
> +-- Revoke all conceivably-relevant ACLs within the extension.  The system
> +-- doesn't check all these ACLs, but this will provide some coverage if that
> +-- ever changes.
> +REVOKE ALL ON TYPE s.citext FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_lt(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_le(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_eq(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_ge(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_gt(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_cmp(s.citext, s.citext) FROM PUBLIC;
> +-- Functions sufficient for making an index column that has the side effect of
> +-- changing search_path at expression planning time.
> +CREATE FUNCTION public.setter() RETURNS bool VOLATILE
> +  LANGUAGE SQL AS $$SET search_path = s; SELECT true$$;
> +CREATE FUNCTION s.const() RETURNS bool IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT public.setter()$$;
> +CREATE FUNCTION s.index_this_expr(s.citext, bool) RETURNS s.citext IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT $1$$;
> +REVOKE ALL ON FUNCTION public.setter() FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.const() FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.index_this_expr(s.citext, bool) FROM PUBLIC;
> +-- Even for an empty table, expression planning calls s.const & public.setter.
> +GRANT EXECUTE ON FUNCTION public.setter() TO regress_minimal;
> +GRANT EXECUTE ON FUNCTION s.const() TO regress_minimal;
> +-- Function for index predicate.
> +CREATE FUNCTION s.index_row_if(s.citext) RETURNS bool IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT $1 IS NOT NULL$$;
> +REVOKE ALL ON FUNCTION s.index_row_if(s.citext) FROM PUBLIC;
> +-- Even for an empty table, CREATE INDEX checks ii_Predicate permissions.
> +GRANT EXECUTE ON FUNCTION s.index_row_if(s.citext) TO regress_minimal;
> +-- Non-extension, non-function objects.
> +CREATE COLLATION s.coll (LOCALE="C");
> +CREATE TABLE s.x (y s.citext);
> +ALTER TABLE s.x OWNER TO regress_minimal;
> +-- Empty-table DefineIndex()
> +CREATE UNIQUE INDEX u0rows ON s.x USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_ops)
> +  WHERE s.index_row_if(y);
> +ALTER TABLE s.x ADD CONSTRAINT e0rows EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +-- Make the table nonempty.
> +INSERT INTO s.x VALUES ('foo'), ('bar');
> +-- If the INSERT runs the planner on index expressions, a search_path change
> +-- survives.  As of 2022-06, the INSERT reuses a cached plan.  It does so even
> +-- under debug_discard_caches, since each index is new-in-transaction.  If
> +-- future work changes a cache lifecycle, this RESET may become necessary.
> +RESET search_path;
> +-- For a nonempty table, owner needs permissions throughout ii_Expressions.
> +GRANT EXECUTE ON FUNCTION s.index_this_expr(s.citext, bool) TO regress_minimal;
> +CREATE UNIQUE INDEX u2rows ON s.x USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_ops)
> +  WHERE s.index_row_if(y);
> +ALTER TABLE s.x ADD CONSTRAINT e2rows EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +-- Shall not find s.coll via search_path, despite the s.const->public.setter
> +-- call having set search_path=s during expression planning.  Suppress the
> +-- message itself, which depends on the database encoding.
> +\set VERBOSITY terse
> +DO $$
> +BEGIN
> +ALTER TABLE s.x ADD CONSTRAINT underqualified EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +EXCEPTION WHEN OTHERS THEN RAISE EXCEPTION '%', sqlstate; END$$;
> +ERROR:  42704
> +\set VERBOSITY default
> +ROLLBACK;
> --- /dev/null
> +++ b/contrib/citext/sql/create_index_acl.sql
> @@ -0,0 +1,82 @@
> +-- Each DefineIndex() ACL check uses either the original userid or the table
> +-- owner userid; see its header comment.  Here, confirm that DefineIndex()
> +-- uses its original userid where necessary.  The test works by creating
> +-- indexes that refer to as many sorts of objects as possible, with the table
> +-- owner having as few applicable privileges as possible.  (The privileges.sql
> +-- regress_sro_user tests look for the opposite defect; they confirm that
> +-- DefineIndex() uses the table owner userid where necessary.)
> +
> +-- Don't override tablespaces; this version lacks allow_in_place_tablespaces.
> +
> +BEGIN;
> +CREATE ROLE regress_minimal;
> +CREATE SCHEMA s;
> +CREATE EXTENSION citext SCHEMA s;
> +-- Revoke all conceivably-relevant ACLs within the extension.  The system
> +-- doesn't check all these ACLs, but this will provide some coverage if that
> +-- ever changes.
> +REVOKE ALL ON TYPE s.citext FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_lt(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_le(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_eq(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_ge(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_gt(s.citext, s.citext) FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.citext_cmp(s.citext, s.citext) FROM PUBLIC;
> +-- Functions sufficient for making an index column that has the side effect of
> +-- changing search_path at expression planning time.
> +CREATE FUNCTION public.setter() RETURNS bool VOLATILE
> +  LANGUAGE SQL AS $$SET search_path = s; SELECT true$$;
> +CREATE FUNCTION s.const() RETURNS bool IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT public.setter()$$;
> +CREATE FUNCTION s.index_this_expr(s.citext, bool) RETURNS s.citext IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT $1$$;
> +REVOKE ALL ON FUNCTION public.setter() FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.const() FROM PUBLIC;
> +REVOKE ALL ON FUNCTION s.index_this_expr(s.citext, bool) FROM PUBLIC;
> +-- Even for an empty table, expression planning calls s.const & public.setter.
> +GRANT EXECUTE ON FUNCTION public.setter() TO regress_minimal;
> +GRANT EXECUTE ON FUNCTION s.const() TO regress_minimal;
> +-- Function for index predicate.
> +CREATE FUNCTION s.index_row_if(s.citext) RETURNS bool IMMUTABLE
> +  LANGUAGE SQL AS $$SELECT $1 IS NOT NULL$$;
> +REVOKE ALL ON FUNCTION s.index_row_if(s.citext) FROM PUBLIC;
> +-- Even for an empty table, CREATE INDEX checks ii_Predicate permissions.
> +GRANT EXECUTE ON FUNCTION s.index_row_if(s.citext) TO regress_minimal;
> +-- Non-extension, non-function objects.
> +CREATE COLLATION s.coll (LOCALE="C");
> +CREATE TABLE s.x (y s.citext);
> +ALTER TABLE s.x OWNER TO regress_minimal;
> +-- Empty-table DefineIndex()
> +CREATE UNIQUE INDEX u0rows ON s.x USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_ops)
> +  WHERE s.index_row_if(y);
> +ALTER TABLE s.x ADD CONSTRAINT e0rows EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +-- Make the table nonempty.
> +INSERT INTO s.x VALUES ('foo'), ('bar');
> +-- If the INSERT runs the planner on index expressions, a search_path change
> +-- survives.  As of 2022-06, the INSERT reuses a cached plan.  It does so even
> +-- under debug_discard_caches, since each index is new-in-transaction.  If
> +-- future work changes a cache lifecycle, this RESET may become necessary.
> +RESET search_path;
> +-- For a nonempty table, owner needs permissions throughout ii_Expressions.
> +GRANT EXECUTE ON FUNCTION s.index_this_expr(s.citext, bool) TO regress_minimal;
> +CREATE UNIQUE INDEX u2rows ON s.x USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll s.citext_ops)
> +  WHERE s.index_row_if(y);
> +ALTER TABLE s.x ADD CONSTRAINT e2rows EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE s.coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +-- Shall not find s.coll via search_path, despite the s.const->public.setter
> +-- call having set search_path=s during expression planning.  Suppress the
> +-- message itself, which depends on the database encoding.
> +\set VERBOSITY terse
> +DO $$
> +BEGIN
> +ALTER TABLE s.x ADD CONSTRAINT underqualified EXCLUDE USING btree
> +  ((s.index_this_expr(y, s.const())) COLLATE coll WITH s.=)
> +  WHERE (s.index_row_if(y));
> +EXCEPTION WHEN OTHERS THEN RAISE EXCEPTION '%', sqlstate; END$$;
> +\set VERBOSITY default
> +ROLLBACK;
> --- a/src/backend/commands/indexcmds.c
> +++ b/src/backend/commands/indexcmds.c
> @@ -65,7 +65,10 @@
>  				  Oid relId,
>  				  char *accessMethodName, Oid accessMethodId,
>  				  bool amcanorder,
> -				  bool isconstraint);
> +				  bool isconstraint,
> +				  Oid ddl_userid,
> +				  int ddl_sec_context,
> +				  int *ddl_save_nestlevel);
>  static Oid GetIndexOpClass(List *opclass, Oid attrType,
>  				char *accessMethodName, Oid accessMethodId);
>  static char *ChooseIndexName(const char *tabname, Oid namespaceId,
> @@ -168,8 +171,7 @@
>  	 * Compute the operator classes, collations, and exclusion operators for
>  	 * the new index, so we can test whether it's compatible with the existing
>  	 * one.  Note that ComputeIndexAttrs might fail here, but that's OK:
> -	 * DefineIndex would have called this function with the same arguments
> -	 * later on, and it would have failed then anyway.
> +	 * DefineIndex would have failed later.
>  	 */
>  	indexInfo = makeNode(IndexInfo);
>  	indexInfo->ii_Expressions = NIL;
> @@ -187,7 +189,7 @@
>  					  coloptions, attributeList,
>  					  exclusionOpNames, relationId,
>  					  accessMethodName, accessMethodId,
> -					  amcanorder, isconstraint);
> +					  amcanorder, isconstraint, InvalidOid, 0, NULL);
>  
>  
>  	/* Get the soon-obsolete pg_index tuple. */
> @@ -280,6 +282,19 @@
>   * DefineIndex
>   *		Creates a new index.
>   *
> + * This function manages the current userid according to the needs of pg_dump.
> + * Recreating old-database catalog entries in new-database is fine, regardless
> + * of which users would have permission to recreate those entries now.  That's
> + * just preservation of state.  Running opaque expressions, like calling a
> + * function named in a catalog entry or evaluating a pg_node_tree in a catalog
> + * entry, as anyone other than the object owner, is not fine.  To adhere to
> + * those principles and to remain fail-safe, use the table owner userid for
> + * most ACL checks.  Use the original userid for ACL checks reached without
> + * traversing opaque expressions.  (pg_dump can predict such ACL checks from
> + * catalogs.)  Overall, this is a mess.  Future DDL development should
> + * consider offering one DDL command for catalog setup and a separate DDL
> + * command for steps that run opaque expressions.
> + *
>   * 'relationId': the OID of the heap relation on which the index is to be
>   *		created
>   * 'stmt': IndexStmt describing the properties of the new index.
> @@ -581,7 +596,8 @@
>  					  coloptions, stmt->indexParams,
>  					  stmt->excludeOpNames, relationId,
>  					  accessMethodName, accessMethodId,
> -					  amcanorder, stmt->isconstraint);
> +					  amcanorder, stmt->isconstraint, root_save_userid,
> +					  root_save_sec_context, &root_save_nestlevel);
>  
>  	/*
>  	 * Extra checks when creating a PRIMARY KEY index.
> @@ -639,9 +655,8 @@
>  
>  	/*
>  	 * Roll back any GUC changes executed by index functions, and keep
> -	 * subsequent changes local to this command.  It's barely possible that
> -	 * some index function changed a behavior-affecting GUC, e.g. xmloption,
> -	 * that affects subsequent steps.
> +	 * subsequent changes local to this command.  This is essential if some
> +	 * index function changed a behavior-affecting GUC, e.g. search_path.
>  	 */
>  	AtEOXact_GUC(false, root_save_nestlevel);
>  	root_save_nestlevel = NewGUCNestLevel();
> @@ -996,6 +1011,10 @@
>  /*
>   * Compute per-index-column information, including indexed column numbers
>   * or index expressions, opclasses, and indoptions.
> + *
> + * If the caller switched to the table owner, ddl_userid is the role for ACL
> + * checks reached without traversing opaque expressions.  Otherwise, it's
> + * InvalidOid, and other ddl_* arguments are undefined.
>   */
>  static void
>  ComputeIndexAttrs(IndexInfo *indexInfo,
> @@ -1009,11 +1028,16 @@
>  				  char *accessMethodName,
>  				  Oid accessMethodId,
>  				  bool amcanorder,
> -				  bool isconstraint)
> +				  bool isconstraint,
> +				  Oid ddl_userid,
> +				  int ddl_sec_context,
> +				  int *ddl_save_nestlevel)
>  {
>  	ListCell   *nextExclOp;
>  	ListCell   *lc;
>  	int			attn;
> +	Oid			save_userid;
> +	int			save_sec_context;
>  
>  	/* Allocate space for exclusion operator info, if needed */
>  	if (exclusionOpNames)
> @@ -1029,6 +1053,9 @@
>  	else
>  		nextExclOp = NULL;
>  
> +	if (OidIsValid(ddl_userid))
> +		GetUserIdAndSecContext(&save_userid, &save_sec_context);
> +
>  	/*
>  	 * process attributeList
>  	 */
> @@ -1123,10 +1150,24 @@
>  		typeOidP[attn] = atttype;
>  
>  		/*
> -		 * Apply collation override if any
> +		 * Apply collation override if any.  Use of ddl_userid is necessary
> +		 * due to ACL checks therein, and it's safe because collations don't
> +		 * contain opaque expressions (or non-opaque expressions).
>  		 */
>  		if (attribute->collation)
> +		{
> +			if (OidIsValid(ddl_userid))
> +			{
> +				AtEOXact_GUC(false, *ddl_save_nestlevel);
> +				SetUserIdAndSecContext(ddl_userid, ddl_sec_context);
> +			}
>  			attcollation = get_collation_oid(attribute->collation, false);
> +			if (OidIsValid(ddl_userid))
> +			{
> +				SetUserIdAndSecContext(save_userid, save_sec_context);
> +				*ddl_save_nestlevel = NewGUCNestLevel();
> +			}
> +		}
>  
>  		/*
>  		 * Check we have a collation iff it's a collatable type.  The only
> @@ -1154,12 +1195,25 @@
>  		collationOidP[attn] = attcollation;
>  
>  		/*
> -		 * Identify the opclass to use.
> +		 * Identify the opclass to use.  Use of ddl_userid is necessary due to
> +		 * ACL checks therein.  This is safe despite opclasses containing
> +		 * opaque expressions (specifically, functions), because only
> +		 * superusers can define opclasses.
>  		 */
> +		if (OidIsValid(ddl_userid))
> +		{
> +			AtEOXact_GUC(false, *ddl_save_nestlevel);
> +			SetUserIdAndSecContext(ddl_userid, ddl_sec_context);
> +		}
>  		classOidP[attn] = GetIndexOpClass(attribute->opclass,
>  										  atttype,
>  										  accessMethodName,
>  										  accessMethodId);
> +		if (OidIsValid(ddl_userid))
> +		{
> +			SetUserIdAndSecContext(save_userid, save_sec_context);
> +			*ddl_save_nestlevel = NewGUCNestLevel();
> +		}
>  
>  		/*
>  		 * Identify the exclusion operator, if any.
> @@ -1173,9 +1227,23 @@
>  
>  			/*
>  			 * Find the operator --- it must accept the column datatype
> -			 * without runtime coercion (but binary compatibility is OK)
> +			 * without runtime coercion (but binary compatibility is OK).
> +			 * Operators contain opaque expressions (specifically, functions).
> +			 * compatible_oper_opid() boils down to oper() and
> +			 * IsBinaryCoercible().  PostgreSQL would have security problems
> +			 * elsewhere if oper() started calling opaque expressions.
>  			 */
> +			if (OidIsValid(ddl_userid))
> +			{
> +				AtEOXact_GUC(false, *ddl_save_nestlevel);
> +				SetUserIdAndSecContext(ddl_userid, ddl_sec_context);
> +			}
>  			opid = compatible_oper_opid(opname, atttype, atttype, false);
> +			if (OidIsValid(ddl_userid))
> +			{
> +				SetUserIdAndSecContext(save_userid, save_sec_context);
> +				*ddl_save_nestlevel = NewGUCNestLevel();
> +			}
>  
>  			/*
>  			 * Only allow commutative operators to be used in exclusion
-- 
Roberto C. Sánchez
http://people.connexer.com/~roberto
http://www.connexer.com
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Dilip Kumar | 2022-07-27 16:19:38 | Re: making relfilenodes 56 bits | 
| Previous Message | Robert Haas | 2022-07-27 15:43:19 | Re: [Commitfest 2022-07] Patch Triage: Waiting on Author |