Make relation-enumerating operations be security-restricted operations.
authorNoah Misch <[email protected]>
Mon, 9 May 2022 15:35:08 +0000 (08:35 -0700)
committerNoah Misch <[email protected]>
Mon, 9 May 2022 15:35:13 +0000 (08:35 -0700)
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

contrib/amcheck/expected/check_btree.out
contrib/amcheck/sql/check_btree.sql
contrib/amcheck/verify_nbtree.c
src/backend/access/brin/brin.c
src/backend/catalog/index.c
src/backend/commands/cluster.c
src/backend/commands/indexcmds.c
src/backend/utils/init/miscinit.c
src/test/regress/expected/privileges.out
src/test/regress/sql/privileges.sql

index 4cd2ddd5ace36fc347c6cdd443ed1cfa902802ff..95c7f84a8fe55d480e207eb3dcf67956dce01f48 100644 (file)
@@ -85,8 +85,31 @@ WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx
 (0 rows)
 
 COMMIT;
+--
+-- Check that index expressions and predicates are run as the table's owner
+--
+TRUNCATE bttest_a;
+INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000);
+ALTER TABLE bttest_a OWNER TO regress_bttest_role;
+-- A dummy index function checking current_user
+CREATE FUNCTION ifun(int8) RETURNS int8 AS $$
+BEGIN
+   ASSERT current_user = 'regress_bttest_role',
+       format('ifun(%s) called by %s', $1, current_user);
+   RETURN $1;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE;
+CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0)))
+   WHERE ifun(id + 10) > ifun(10);
+SELECT bt_index_check('bttest_a_expr_idx');
+ bt_index_check 
+----------------
+(1 row)
+
 -- cleanup
 DROP TABLE bttest_a;
 DROP TABLE bttest_b;
+DROP FUNCTION ifun(int8);
 DROP OWNED BY regress_bttest_role; -- permissions
 DROP ROLE regress_bttest_role;
index 019e0b74a4e75a62faf34dda377b42196db31789..91b77ebd38924ef73364741bc8078a6e0bac5e25 100644 (file)
@@ -54,8 +54,29 @@ WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx
     AND pid = pg_backend_pid();
 COMMIT;
 
+--
+-- Check that index expressions and predicates are run as the table's owner
+--
+TRUNCATE bttest_a;
+INSERT INTO bttest_a SELECT * FROM generate_series(1, 1000);
+ALTER TABLE bttest_a OWNER TO regress_bttest_role;
+-- A dummy index function checking current_user
+CREATE FUNCTION ifun(int8) RETURNS int8 AS $$
+BEGIN
+   ASSERT current_user = 'regress_bttest_role',
+       format('ifun(%s) called by %s', $1, current_user);
+   RETURN $1;
+END;
+$$ LANGUAGE plpgsql IMMUTABLE;
+
+CREATE INDEX bttest_a_expr_idx ON bttest_a ((ifun(id) + ifun(0)))
+   WHERE ifun(id + 10) > ifun(10);
+
+SELECT bt_index_check('bttest_a_expr_idx');
+
 -- cleanup
 DROP TABLE bttest_a;
 DROP TABLE bttest_b;
+DROP FUNCTION ifun(int8);
 DROP OWNED BY regress_bttest_role; -- permissions
 DROP ROLE regress_bttest_role;
index 6c496c5e0e319ee10886b356c8906b065dabe22f..29b4170a7eb055dc9f85c1fcfb7a88526957a5dd 100644 (file)
@@ -26,6 +26,7 @@
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/smgr.h"
+#include "utils/guc.h"
 #include "utils/memutils.h"
 #include "utils/snapmgr.h"
 
@@ -163,6 +164,9 @@ bt_index_check_internal(Oid indrelid, bool parentcheck)
    Relation    indrel;
    Relation    heaprel;
    LOCKMODE    lockmode;
+   Oid         save_userid;
+   int         save_sec_context;
+   int         save_nestlevel;
 
    if (parentcheck)
        lockmode = ShareLock;
@@ -179,9 +183,27 @@ bt_index_check_internal(Oid indrelid, bool parentcheck)
     */
    heapid = IndexGetRelation(indrelid, true);
    if (OidIsValid(heapid))
+   {
        heaprel = heap_open(heapid, lockmode);
+
+       /*
+        * Switch to the table owner's userid, so that any 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(heaprel->rd_rel->relowner,
+                              save_sec_context | SECURITY_RESTRICTED_OPERATION);
+       save_nestlevel = NewGUCNestLevel();
+   }
    else
+   {
        heaprel = NULL;
+       /* for "gcc -Og" https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78394 */
+       save_userid = InvalidOid;
+       save_sec_context = -1;
+       save_nestlevel = -1;
+   }
 
    /*
     * Open the target index relations separately (like relation_openrv(), but
@@ -219,6 +241,12 @@ bt_index_check_internal(Oid indrelid, bool parentcheck)
        bt_check_every_level(indrel, parentcheck);
    }
 
+   /* Roll back any GUC changes executed by index functions */
+   AtEOXact_GUC(false, save_nestlevel);
+
+   /* Restore userid and security context */
+   SetUserIdAndSecContext(save_userid, save_sec_context);
+
    /*
     * Release locks early. That's ok here because nothing in the called
     * routines will trigger shared cache invalidations to be sent, so we can
index ccecd00a7e6a6535ed7c86e02990b86716f2f18d..fdedb6252bf6f4880b7839710425c689c7b675a8 100644 (file)
@@ -30,6 +30,7 @@
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "utils/builtins.h"
+#include "utils/guc.h"
 #include "utils/index_selfuncs.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -866,6 +867,9 @@ brin_summarize_range(PG_FUNCTION_ARGS)
    Oid         heapoid;
    Relation    indexRel;
    Relation    heapRel;
+   Oid         save_userid;
+   int         save_sec_context;
+   int         save_nestlevel;
    double      numSummarized = 0;
 
    if (RecoveryInProgress())
@@ -892,7 +896,22 @@ brin_summarize_range(PG_FUNCTION_ARGS)
     */
    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;
 
@@ -907,7 +926,7 @@ brin_summarize_range(PG_FUNCTION_ARGS)
                        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));
 
@@ -925,6 +944,12 @@ brin_summarize_range(PG_FUNCTION_ARGS)
    /* OK, do it */
    brinsummarize(indexRel, heapRel, heapBlk, true, &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);
 
@@ -966,6 +991,9 @@ brin_desummarize_range(PG_FUNCTION_ARGS)
     * 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))
index 28428a7665fe74edfc573ce12370955953517888..4176221dffd0952bf13f37c39b1fe60f99ba06aa 100644 (file)
@@ -2926,7 +2926,17 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 
    /* 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);
 
    /*
@@ -2939,16 +2949,6 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
    /* mark build is concurrent just for consistency */
    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.
     */
@@ -3411,6 +3411,9 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
    Relation    iRel,
                heapRelation;
    Oid         heapId;
+   Oid         save_userid;
+   int         save_sec_context;
+   int         save_nestlevel;
    IndexInfo  *indexInfo;
    volatile bool skipped_constraint = false;
    PGRUsage    ru0;
@@ -3424,6 +3427,16 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
    heapId = IndexGetRelation(indexId, false);
    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.
@@ -3565,6 +3578,12 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
                 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);
index 69dbaf3b8d426add465be7b58ade0ff2a3e11eb4..615845e536c7a63824a5455ea078e4aa22b21563 100644 (file)
@@ -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 @@ void
 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 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
    if (!OldHeap)
        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 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
        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 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
        if (RELATION_IS_OTHER_TEMP(OldHeap))
        {
            relation_close(OldHeap, AccessExclusiveLock);
-           return;
+           goto out;
        }
 
        if (OidIsValid(indexOid))
@@ -318,7 +332,7 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
            if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
            {
                relation_close(OldHeap, AccessExclusiveLock);
-               return;
+               goto out;
            }
 
            /*
@@ -328,14 +342,14 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
            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 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
        !RelationIsPopulated(OldHeap))
    {
        relation_close(OldHeap, AccessExclusiveLock);
-       return;
+       goto out;
    }
 
    /*
@@ -404,6 +418,13 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose)
    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);
 }
 
 /*
index 292ba8d9b6ff83d1f0240a971d1c1519b9a32995..7652dc854e613362fc39e4697d4a2ba5613fbf00 100644 (file)
@@ -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"
@@ -344,8 +345,13 @@ DefineIndex(Oid relationId,
    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
@@ -386,6 +392,15 @@ DefineIndex(Oid relationId,
    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);
 
@@ -441,7 +456,7 @@ DefineIndex(Oid relationId,
    {
        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,
@@ -468,7 +483,7 @@ DefineIndex(Oid relationId,
    {
        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,
@@ -695,15 +710,33 @@ DefineIndex(Oid relationId,
 
    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 */
@@ -782,6 +815,16 @@ DefineIndex(Oid relationId,
    /* 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);
 
@@ -797,6 +840,12 @@ DefineIndex(Oid relationId,
    /* 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);
index 8bc4c06b449f209ac435a4dcca157056319608ea..bd6d8849e65222e0cec0b89f853f7b0230d84e62 100644 (file)
@@ -367,15 +367,21 @@ GetAuthenticatedUserId(void)
  * 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
index ada059244ac3b35ec77a770112e7db73df10c326..6c812f70a64edb4819dcd5d5cc2542680340fa86 100644 (file)
@@ -1263,6 +1263,69 @@ SELECT has_table_privilege('regress_user1', 'atest4', 'SELECT WITH GRANT OPTION'
 -- 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_desummarize_range('sro_brin', 0);
+ brin_desummarize_range 
+------------------------
+(1 row)
+
+SELECT brin_summarize_range('sro_brin', 0);
+ brin_summarize_range 
+----------------------
+                    1
+(1 row)
+
+DROP TABLE sro_tab;
+-- Check with a partitioned table
+CREATE TABLE sro_ptab (a int) PARTITION BY RANGE (a);
+ALTER TABLE sro_ptab OWNER TO regress_sro_user;
+CREATE TABLE sro_part PARTITION OF sro_ptab FOR VALUES FROM (1) TO (10);
+ALTER TABLE sro_part OWNER TO regress_sro_user;
+INSERT INTO sro_ptab VALUES (1), (2), (3);
+CREATE INDEX sro_pidx ON sro_ptab ((sro_ifun(a) + sro_ifun(0)))
+   WHERE sro_ifun(a + 10) > sro_ifun(10);
+ERROR:  cannot create index on partitioned table "sro_ptab"
+REINDEX TABLE sro_ptab;
+NOTICE:  table "sro_ptab" has no indexes
+REINDEX INDEX CONCURRENTLY sro_pidx;  -- v12+ feature
+ERROR:  syntax error at or near "CONCURRENTLY"
+LINE 1: REINDEX INDEX CONCURRENTLY sro_pidx;
+                      ^
 SET SESSION AUTHORIZATION regress_sro_user;
 CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS
    'GRANT regress_group2 TO regress_sro_user';
index 7edfc28f845319cf452263cf17d6e3e5ec4b6c4a..81a86ebbf46fb2bbecf36adef1115db37bc46fe7 100644 (file)
@@ -771,6 +771,53 @@ SELECT has_table_privilege('regress_user1', 'atest4', 'SELECT WITH GRANT OPTION'
 \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_desummarize_range('sro_brin', 0);
+SELECT brin_summarize_range('sro_brin', 0);
+DROP TABLE sro_tab;
+-- Check with a partitioned table
+CREATE TABLE sro_ptab (a int) PARTITION BY RANGE (a);
+ALTER TABLE sro_ptab OWNER TO regress_sro_user;
+CREATE TABLE sro_part PARTITION OF sro_ptab FOR VALUES FROM (1) TO (10);
+ALTER TABLE sro_part OWNER TO regress_sro_user;
+INSERT INTO sro_ptab VALUES (1), (2), (3);
+CREATE INDEX sro_pidx ON sro_ptab ((sro_ifun(a) + sro_ifun(0)))
+   WHERE sro_ifun(a + 10) > sro_ifun(10);
+REINDEX TABLE sro_ptab;
+REINDEX INDEX CONCURRENTLY sro_pidx;  -- v12+ feature
+
 SET SESSION AUTHORIZATION regress_sro_user;
 CREATE FUNCTION unwanted_grant() RETURNS void LANGUAGE sql AS
    'GRANT regress_group2 TO regress_sro_user';