Switch user ID to the object owner when populating a materialized view.
authorNoah Misch <noah@leadboat.com>
Fri, 12 Jul 2013 22:21:22 +0000 (18:21 -0400)
committerNoah Misch <noah@leadboat.com>
Fri, 12 Jul 2013 22:21:22 +0000 (18:21 -0400)
This makes superuser-issued REFRESH MATERIALIZED VIEW safe regardless of
the object's provenance.  REINDEX is an earlier example of this pattern.
As a downside, functions called from materialized views must tolerate
running in a security-restricted operation.  CREATE MATERIALIZED VIEW
need not change user ID.  Nonetheless, avoid creation of materialized
views that will invariably fail REFRESH by making it, too, start a
security-restricted operation.

Back-patch to 9.3 so materialized views have this from the beginning.

Reviewed by Kevin Grittner.

doc/src/sgml/ref/create_materialized_view.sgml
src/backend/commands/createas.c
src/backend/commands/matview.c

index 0ed764b353394137afd73c9a013e031e197ae79c..b742e17ac828a64f816650e7c8c78c3f67bffd78 100644 (file)
@@ -105,7 +105,9 @@ CREATE MATERIALIZED VIEW <replaceable>table_name</replaceable>
     <listitem>
      <para>
       A <xref linkend="sql-select">, <link linkend="sql-table">TABLE</link>,
-      or <xref linkend="sql-values"> command.
+      or <xref linkend="sql-values"> command.  This query will run within a
+      security-restricted operation; in particular, calls to functions that
+      themselves create temporary tables will fail.
      </para>
     </listitem>
    </varlistentry>
index 2bfe5fba8775631afb378b8591b536c3472b074b..a3509d8c2a3242efa7896fb0a777e5928b239d6e 100644 (file)
@@ -33,6 +33,7 @@
 #include "commands/prepare.h"
 #include "commands/tablecmds.h"
 #include "commands/view.h"
+#include "miscadmin.h"
 #include "parser/parse_clause.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/smgr.h"
@@ -69,7 +70,11 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
 {
    Query      *query = (Query *) stmt->query;
    IntoClause *into = stmt->into;
+   bool        is_matview = (into->viewQuery != NULL);
    DestReceiver *dest;
+   Oid         save_userid = InvalidOid;
+   int         save_sec_context = 0;
+   int         save_nestlevel = 0;
    List       *rewritten;
    PlannedStmt *plan;
    QueryDesc  *queryDesc;
@@ -90,12 +95,28 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
    {
        ExecuteStmt *estmt = (ExecuteStmt *) query->utilityStmt;
 
+       Assert(!is_matview);    /* excluded by syntax */
        ExecuteQuery(estmt, into, queryString, params, dest, completionTag);
 
        return;
    }
    Assert(query->commandType == CMD_SELECT);
 
+   /*
+    * For materialized views, lock down security-restricted operations and
+    * arrange to make GUC variable changes local to this command.  This is
+    * not necessary for security, but this keeps the behavior similar to
+    * REFRESH MATERIALIZED VIEW.  Otherwise, one could create a materialized
+    * view not possible to refresh.
+    */
+   if (is_matview)
+   {
+       GetUserIdAndSecContext(&save_userid, &save_sec_context);
+       SetUserIdAndSecContext(save_userid,
+                          save_sec_context | SECURITY_RESTRICTED_OPERATION);
+       save_nestlevel = NewGUCNestLevel();
+   }
+
    /*
     * Parse analysis was done already, but we still have to run the rule
     * rewriter.  We do not do AcquireRewriteLocks: we assume the query either
@@ -160,6 +181,15 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
    FreeQueryDesc(queryDesc);
 
    PopActiveSnapshot();
+
+   if (is_matview)
+   {
+       /* Roll back any GUC changes */
+       AtEOXact_GUC(false, save_nestlevel);
+
+       /* Restore userid and security context */
+       SetUserIdAndSecContext(save_userid, save_sec_context);
+   }
 }
 
 /*
index 2ffdca31f6ba35e2c95d75fcba5bf850f7a20aa3..1c383baf68750808320ebc5e9925b122c802a639 100644 (file)
@@ -122,6 +122,9 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
    RewriteRule *rule;
    List       *actions;
    Query      *dataQuery;
+   Oid         save_userid;
+   int         save_sec_context;
+   int         save_nestlevel;
    Oid         tableSpace;
    Oid         OIDNewHeap;
    DestReceiver *dest;
@@ -191,6 +194,16 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
     */
    CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW");
 
+   /*
+    * 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();
+
    /*
     * Tentatively mark the matview as populated or not (this will roll back
     * if we fail later).
@@ -217,6 +230,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
                     RecentXmin, ReadNextMultiXactId());
 
    RelationCacheInvalidateEntry(matviewOid);
+
+   /* Roll back any GUC changes */
+   AtEOXact_GUC(false, save_nestlevel);
+
+   /* Restore userid and security context */
+   SetUserIdAndSecContext(save_userid, save_sec_context);
 }
 
 /*