Commit a9d0f1cf authored by Kevin Grittner's avatar Kevin Grittner

Fix superuser concurrent refresh of matview owned by another.

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.
parent 5569d75d
...@@ -59,12 +59,13 @@ static void transientrel_receive(TupleTableSlot *slot, DestReceiver *self); ...@@ -59,12 +59,13 @@ static void transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
static void transientrel_shutdown(DestReceiver *self); static void transientrel_shutdown(DestReceiver *self);
static void transientrel_destroy(DestReceiver *self); static void transientrel_destroy(DestReceiver *self);
static void refresh_matview_datafill(DestReceiver *dest, Query *query, 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 char *make_temptable_name_n(char *tempname, int n);
static void mv_GenerateOper(StringInfo buf, Oid opoid); 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 refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap);
static void OpenMatViewIncrementalMaintenance(void); static void OpenMatViewIncrementalMaintenance(void);
...@@ -142,12 +143,15 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ...@@ -142,12 +143,15 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
List *actions; List *actions;
Query *dataQuery; Query *dataQuery;
Oid tableSpace; Oid tableSpace;
Oid owner; Oid relowner;
Oid OIDNewHeap; Oid OIDNewHeap;
DestReceiver *dest; DestReceiver *dest;
bool concurrent; bool concurrent;
LOCKMODE lockmode; LOCKMODE lockmode;
char relpersistence; char relpersistence;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
/* Determine strength of lock needed. */ /* Determine strength of lock needed. */
concurrent = stmt->concurrent; concurrent = stmt->concurrent;
...@@ -232,6 +236,19 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ...@@ -232,6 +236,19 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
*/ */
SetMatViewPopulatedState(matviewRel, !stmt->skipData); 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. */ /* Concurrent refresh builds new data in temp tablespace, and does diff. */
if (concurrent) if (concurrent)
{ {
...@@ -244,8 +261,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ...@@ -244,8 +261,6 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
relpersistence = matviewRel->rd_rel->relpersistence; relpersistence = matviewRel->rd_rel->relpersistence;
} }
owner = matviewRel->rd_rel->relowner;
/* /*
* Create the transient table that will receive the regenerated data. Lock * Create the transient table that will receive the regenerated data. Lock
* it against access by any other process until commit (by which time it * it against access by any other process until commit (by which time it
...@@ -256,9 +271,15 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ...@@ -256,9 +271,15 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
LockRelationOid(OIDNewHeap, AccessExclusiveLock); LockRelationOid(OIDNewHeap, AccessExclusiveLock);
dest = CreateTransientRelDestReceiver(OIDNewHeap); dest = CreateTransientRelDestReceiver(OIDNewHeap);
/*
* Now lock down security-restricted operations.
*/
SetUserIdAndSecContext(relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
/* Generate the data, if wanted. */ /* Generate the data, if wanted. */
if (!stmt->skipData) if (!stmt->skipData)
refresh_matview_datafill(dest, dataQuery, queryString, owner); refresh_matview_datafill(dest, dataQuery, queryString);
heap_close(matviewRel, NoLock); heap_close(matviewRel, NoLock);
...@@ -269,7 +290,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ...@@ -269,7 +290,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
PG_TRY(); PG_TRY();
{ {
refresh_by_match_merge(matviewOid, OIDNewHeap); refresh_by_match_merge(matviewOid, OIDNewHeap, relowner,
save_sec_context);
} }
PG_CATCH(); PG_CATCH();
{ {
...@@ -282,6 +304,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ...@@ -282,6 +304,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
else else
refresh_by_heap_swap(matviewOid, OIDNewHeap); 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);
return matviewOid; return matviewOid;
} }
...@@ -290,26 +318,13 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ...@@ -290,26 +318,13 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
*/ */
static void static void
refresh_matview_datafill(DestReceiver *dest, Query *query, refresh_matview_datafill(DestReceiver *dest, Query *query,
const char *queryString, Oid relowner) const char *queryString)
{ {
List *rewritten; List *rewritten;
PlannedStmt *plan; PlannedStmt *plan;
QueryDesc *queryDesc; QueryDesc *queryDesc;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
Query *copied_query; 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. */ /* Lock and rewrite, using a copy to preserve the original query. */
copied_query = copyObject(query); copied_query = copyObject(query);
AcquireRewriteLocks(copied_query, true, false); AcquireRewriteLocks(copied_query, true, false);
...@@ -353,12 +368,6 @@ refresh_matview_datafill(DestReceiver *dest, Query *query, ...@@ -353,12 +368,6 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
FreeQueryDesc(queryDesc); FreeQueryDesc(queryDesc);
PopActiveSnapshot(); PopActiveSnapshot();
/* Roll back any GUC changes */
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
} }
DestReceiver * DestReceiver *
...@@ -529,7 +538,8 @@ mv_GenerateOper(StringInfo buf, Oid opoid) ...@@ -529,7 +538,8 @@ mv_GenerateOper(StringInfo buf, Oid opoid)
* this command. * this command.
*/ */
static void 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; StringInfoData querybuf;
Relation matviewRel; Relation matviewRel;
...@@ -543,9 +553,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid) ...@@ -543,9 +553,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
ListCell *indexoidscan; ListCell *indexoidscan;
int16 relnatts; int16 relnatts;
bool *usedForQual; bool *usedForQual;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
initStringInfo(&querybuf); initStringInfo(&querybuf);
matviewRel = heap_open(matviewOid, NoLock); matviewRel = heap_open(matviewOid, NoLock);
...@@ -596,6 +603,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid) ...@@ -596,6 +603,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1)))); 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. */ /* Start building the query for creating the diff table. */
resetStringInfo(&querybuf); resetStringInfo(&querybuf);
appendStringInfo(&querybuf, appendStringInfo(&querybuf,
...@@ -690,9 +700,12 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid) ...@@ -690,9 +700,12 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY) if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
elog(ERROR, "SPI_exec failed: %s", querybuf.data); 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 * 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. */ /* Analyze the diff table. */
...@@ -703,16 +716,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid) ...@@ -703,16 +716,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
OpenMatViewIncrementalMaintenance(); 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. */ /* Deletes must come before inserts; do them first. */
resetStringInfo(&querybuf); resetStringInfo(&querybuf);
appendStringInfo(&querybuf, appendStringInfo(&querybuf,
...@@ -733,12 +736,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid) ...@@ -733,12 +736,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT) if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
elog(ERROR, "SPI_exec failed: %s", querybuf.data); 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. */ /* We're done maintaining the materialized view. */
CloseMatViewIncrementalMaintenance(); CloseMatViewIncrementalMaintenance();
heap_close(tempRel, NoLock); heap_close(tempRel, NoLock);
......
...@@ -502,3 +502,15 @@ SELECT * FROM mv_v; ...@@ -502,3 +502,15 @@ SELECT * FROM mv_v;
DROP TABLE v CASCADE; DROP TABLE v CASCADE;
NOTICE: drop cascades to materialized view mv_v 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;
...@@ -194,3 +194,16 @@ DELETE FROM v WHERE EXISTS ( SELECT * FROM mv_v WHERE mv_v.a = v.a ); ...@@ -194,3 +194,16 @@ DELETE FROM v WHERE EXISTS ( SELECT * FROM mv_v WHERE mv_v.a = v.a );
SELECT * FROM v; SELECT * FROM v;
SELECT * FROM mv_v; SELECT * FROM mv_v;
DROP TABLE v CASCADE; 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;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment