]> granicus.if.org Git - postgresql/commitdiff
Fix superuser concurrent refresh of matview owned by another.
authorKevin Grittner <kgrittn@postgresql.org>
Tue, 26 Aug 2014 15:00:42 +0000 (10:00 -0500)
committerKevin Grittner <kgrittn@postgresql.org>
Tue, 26 Aug 2014 15:00:42 +0000 (10:00 -0500)
Use SECURITY_LOCAL_USERID_CHANGE while building temporary tables;
only escalate to SECURITY_RESTRICTED_OPERATION while potentially
running user-supplied code.  The more secure mode was preventing
temp table creation.  Add regression tests to cover this problem.

This fixes Bug #11208 reported by Bruno Emanuel de Andrade Silva.

Backpatch to 9.4, where the bug was introduced.

src/backend/commands/matview.c
src/test/regress/expected/matview.out
src/test/regress/sql/matview.sql

index 5130d512a6a836dfb145cc4844b4e976d437446c..79dee7873cd4c191b4123cb081680ae7aa9cdd79 100644 (file)
@@ -59,12 +59,13 @@ static void transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void transientrel_shutdown(DestReceiver *self);
 static void transientrel_destroy(DestReceiver *self);
 static void refresh_matview_datafill(DestReceiver *dest, Query *query,
-                                                const char *queryString, Oid relowner);
+                                                const char *queryString);
 
 static char *make_temptable_name_n(char *tempname, int n);
 static void mv_GenerateOper(StringInfo buf, Oid opoid);
 
-static void refresh_by_match_merge(Oid matviewOid, Oid tempOid);
+static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
+                                                int save_sec_context);
 static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap);
 
 static void OpenMatViewIncrementalMaintenance(void);
@@ -142,11 +143,14 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
        List       *actions;
        Query      *dataQuery;
        Oid                     tableSpace;
-       Oid                     owner;
+       Oid                     relowner;
        Oid                     OIDNewHeap;
        DestReceiver *dest;
        bool            concurrent;
        LOCKMODE        lockmode;
+       Oid                     save_userid;
+       int                     save_sec_context;
+       int                     save_nestlevel;
 
        /* Determine strength of lock needed. */
        concurrent = stmt->concurrent;
@@ -231,14 +235,25 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
         */
        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);
        else
                tableSpace = matviewRel->rd_rel->reltablespace;
 
-       owner = matviewRel->rd_rel->relowner;
-
        /*
         * Create the transient table that will receive the regenerated data. Lock
         * it against access by any other process until commit (by which time it
@@ -249,9 +264,15 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
        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, owner);
+               refresh_matview_datafill(dest, dataQuery, queryString);
 
        heap_close(matviewRel, NoLock);
 
@@ -262,7 +283,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
 
                PG_TRY();
                {
-                       refresh_by_match_merge(matviewOid, OIDNewHeap);
+                       refresh_by_match_merge(matviewOid, OIDNewHeap, relowner,
+                                                                  save_sec_context);
                }
                PG_CATCH();
                {
@@ -274,6 +296,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
        }
        else
                refresh_by_heap_swap(matviewOid, OIDNewHeap);
+
+       /* Roll back any GUC changes */
+       AtEOXact_GUC(false, save_nestlevel);
+
+       /* Restore userid and security context */
+       SetUserIdAndSecContext(save_userid, save_sec_context);
 }
 
 /*
@@ -281,26 +309,13 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
  */
 static void
 refresh_matview_datafill(DestReceiver *dest, Query *query,
-                                                const char *queryString, Oid relowner)
+                                                const char *queryString)
 {
        List       *rewritten;
        PlannedStmt *plan;
        QueryDesc  *queryDesc;
-       Oid                     save_userid;
-       int                     save_sec_context;
-       int                     save_nestlevel;
        Query      *copied_query;
 
-       /*
-        * 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();
-
        /* Lock and rewrite, using a copy to preserve the original query. */
        copied_query = copyObject(query);
        AcquireRewriteLocks(copied_query, true, false);
@@ -344,12 +359,6 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
        FreeQueryDesc(queryDesc);
 
        PopActiveSnapshot();
-
-       /* Roll back any GUC changes */
-       AtEOXact_GUC(false, save_nestlevel);
-
-       /* Restore userid and security context */
-       SetUserIdAndSecContext(save_userid, save_sec_context);
 }
 
 DestReceiver *
@@ -520,7 +529,8 @@ mv_GenerateOper(StringInfo buf, Oid opoid)
  * this command.
  */
 static void
-refresh_by_match_merge(Oid matviewOid, Oid tempOid)
+refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
+                                          int save_sec_context)
 {
        StringInfoData querybuf;
        Relation        matviewRel;
@@ -534,9 +544,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
        ListCell   *indexoidscan;
        int16           relnatts;
        bool       *usedForQual;
-       Oid                     save_userid;
-       int                     save_sec_context;
-       int                     save_nestlevel;
 
        initStringInfo(&querybuf);
        matviewRel = heap_open(matviewOid, NoLock);
@@ -587,6 +594,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
                        SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1))));
        }
 
+       SetUserIdAndSecContext(relowner,
+                                                  save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
        /* Start building the query for creating the diff table. */
        resetStringInfo(&querybuf);
        appendStringInfo(&querybuf,
@@ -681,9 +691,12 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
        if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
                elog(ERROR, "SPI_exec failed: %s", querybuf.data);
 
+       SetUserIdAndSecContext(relowner,
+                                                  save_sec_context | SECURITY_RESTRICTED_OPERATION);
+
        /*
         * We have no further use for data from the "full-data" temp table, but we
-        * must keep it around because its type is reference from the diff table.
+        * must keep it around because its type is referenced from the diff table.
         */
 
        /* Analyze the diff table. */
@@ -694,16 +707,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
 
        OpenMatViewIncrementalMaintenance();
 
-       /*
-        * 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(matviewRel->rd_rel->relowner,
-                                                  save_sec_context | SECURITY_RESTRICTED_OPERATION);
-       save_nestlevel = NewGUCNestLevel();
-
        /* Deletes must come before inserts; do them first. */
        resetStringInfo(&querybuf);
        appendStringInfo(&querybuf,
@@ -724,12 +727,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
        if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
                elog(ERROR, "SPI_exec failed: %s", querybuf.data);
 
-       /* Roll back any GUC changes */
-       AtEOXact_GUC(false, save_nestlevel);
-
-       /* Restore userid and security context */
-       SetUserIdAndSecContext(save_userid, save_sec_context);
-
        /* We're done maintaining the materialized view. */
        CloseMatViewIncrementalMaintenance();
        heap_close(tempRel, NoLock);
index ddac97bea66e5788c3fec30e6ec8120646b0d31f..b04cb9316973aafc4897288d2cbc9b9a57cb2603 100644 (file)
@@ -502,3 +502,15 @@ SELECT * FROM mv_v;
 
 DROP TABLE v CASCADE;
 NOTICE:  drop cascades to materialized view mv_v
+-- make sure running as superuser works when MV owned by another role (bug #11208)
+CREATE ROLE user_dw;
+SET ROLE user_dw;
+CREATE TABLE foo_data AS SELECT i, md5(random()::text)
+  FROM generate_series(1, 10) i;
+CREATE MATERIALIZED VIEW mv_foo AS SELECT * FROM foo_data;
+CREATE UNIQUE INDEX ON mv_foo (i);
+RESET ROLE;
+REFRESH MATERIALIZED VIEW mv_foo;
+REFRESH MATERIALIZED VIEW CONCURRENTLY mv_foo;
+DROP OWNED BY user_dw CASCADE;
+DROP ROLE user_dw;
index 3a6a3276f844553b3a8f5ba420e54ec020c9090c..fee1ddc8424726fe42721a9af8c1bdd6e0a552af 100644 (file)
@@ -194,3 +194,16 @@ DELETE FROM v WHERE EXISTS ( SELECT * FROM mv_v WHERE mv_v.a = v.a );
 SELECT * FROM v;
 SELECT * FROM mv_v;
 DROP TABLE v CASCADE;
+
+-- make sure running as superuser works when MV owned by another role (bug #11208)
+CREATE ROLE user_dw;
+SET ROLE user_dw;
+CREATE TABLE foo_data AS SELECT i, md5(random()::text)
+  FROM generate_series(1, 10) i;
+CREATE MATERIALIZED VIEW mv_foo AS SELECT * FROM foo_data;
+CREATE UNIQUE INDEX ON mv_foo (i);
+RESET ROLE;
+REFRESH MATERIALIZED VIEW mv_foo;
+REFRESH MATERIALIZED VIEW CONCURRENTLY mv_foo;
+DROP OWNED BY user_dw CASCADE;
+DROP ROLE user_dw;