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?= <robe...@debian.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 <n...@leadboat.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 <n...@leadboat.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 <n...@leadboat.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/f8a4105f076544c180a87ef0c4822...@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 <n...@leadboat.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 <n...@leadboat.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 <n...@leadboat.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/f8a4105f076544c180a87ef0c4822...@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


Reply via email to