Skip to content

Commit 06414c0

Browse files
committed
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.
1 parent 3a3d3f9 commit 06414c0

File tree

3 files changed

+70
-48
lines changed

3 files changed

+70
-48
lines changed

src/backend/commands/matview.c

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ static void transientrel_receive(TupleTableSlot *slot, DestReceiver *self);
5959
static void transientrel_shutdown(DestReceiver *self);
6060
static void transientrel_destroy(DestReceiver *self);
6161
static void refresh_matview_datafill(DestReceiver *dest, Query *query,
62-
const char *queryString, Oid relowner);
62+
const char *queryString);
6363

6464
static char *make_temptable_name_n(char *tempname, int n);
6565
static void mv_GenerateOper(StringInfo buf, Oid opoid);
6666

67-
static void refresh_by_match_merge(Oid matviewOid, Oid tempOid);
67+
static void refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
68+
int save_sec_context);
6869
static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap);
6970

7071
static void OpenMatViewIncrementalMaintenance(void);
@@ -142,11 +143,14 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
142143
List *actions;
143144
Query *dataQuery;
144145
Oid tableSpace;
145-
Oid owner;
146+
Oid relowner;
146147
Oid OIDNewHeap;
147148
DestReceiver *dest;
148149
bool concurrent;
149150
LOCKMODE lockmode;
151+
Oid save_userid;
152+
int save_sec_context;
153+
int save_nestlevel;
150154

151155
/* Determine strength of lock needed. */
152156
concurrent = stmt->concurrent;
@@ -231,14 +235,25 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
231235
*/
232236
SetMatViewPopulatedState(matviewRel, !stmt->skipData);
233237

238+
relowner = matviewRel->rd_rel->relowner;
239+
240+
/*
241+
* Switch to the owner's userid, so that any functions are run as that
242+
* user. Also arrange to make GUC variable changes local to this command.
243+
* Don't lock it down too tight to create a temporary table just yet. We
244+
* will switch modes when we are about to execute user code.
245+
*/
246+
GetUserIdAndSecContext(&save_userid, &save_sec_context);
247+
SetUserIdAndSecContext(relowner,
248+
save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
249+
save_nestlevel = NewGUCNestLevel();
250+
234251
/* Concurrent refresh builds new data in temp tablespace, and does diff. */
235252
if (concurrent)
236253
tableSpace = GetDefaultTablespace(RELPERSISTENCE_TEMP);
237254
else
238255
tableSpace = matviewRel->rd_rel->reltablespace;
239256

240-
owner = matviewRel->rd_rel->relowner;
241-
242257
/*
243258
* Create the transient table that will receive the regenerated data. Lock
244259
* it against access by any other process until commit (by which time it
@@ -249,9 +264,15 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
249264
LockRelationOid(OIDNewHeap, AccessExclusiveLock);
250265
dest = CreateTransientRelDestReceiver(OIDNewHeap);
251266

267+
/*
268+
* Now lock down security-restricted operations.
269+
*/
270+
SetUserIdAndSecContext(relowner,
271+
save_sec_context | SECURITY_RESTRICTED_OPERATION);
272+
252273
/* Generate the data, if wanted. */
253274
if (!stmt->skipData)
254-
refresh_matview_datafill(dest, dataQuery, queryString, owner);
275+
refresh_matview_datafill(dest, dataQuery, queryString);
255276

256277
heap_close(matviewRel, NoLock);
257278

@@ -262,7 +283,8 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
262283

263284
PG_TRY();
264285
{
265-
refresh_by_match_merge(matviewOid, OIDNewHeap);
286+
refresh_by_match_merge(matviewOid, OIDNewHeap, relowner,
287+
save_sec_context);
266288
}
267289
PG_CATCH();
268290
{
@@ -274,33 +296,26 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
274296
}
275297
else
276298
refresh_by_heap_swap(matviewOid, OIDNewHeap);
299+
300+
/* Roll back any GUC changes */
301+
AtEOXact_GUC(false, save_nestlevel);
302+
303+
/* Restore userid and security context */
304+
SetUserIdAndSecContext(save_userid, save_sec_context);
277305
}
278306

279307
/*
280308
* refresh_matview_datafill
281309
*/
282310
static void
283311
refresh_matview_datafill(DestReceiver *dest, Query *query,
284-
const char *queryString, Oid relowner)
312+
const char *queryString)
285313
{
286314
List *rewritten;
287315
PlannedStmt *plan;
288316
QueryDesc *queryDesc;
289-
Oid save_userid;
290-
int save_sec_context;
291-
int save_nestlevel;
292317
Query *copied_query;
293318

294-
/*
295-
* Switch to the owner's userid, so that any functions are run as that
296-
* user. Also lock down security-restricted operations and arrange to
297-
* make GUC variable changes local to this command.
298-
*/
299-
GetUserIdAndSecContext(&save_userid, &save_sec_context);
300-
SetUserIdAndSecContext(relowner,
301-
save_sec_context | SECURITY_RESTRICTED_OPERATION);
302-
save_nestlevel = NewGUCNestLevel();
303-
304319
/* Lock and rewrite, using a copy to preserve the original query. */
305320
copied_query = copyObject(query);
306321
AcquireRewriteLocks(copied_query, true, false);
@@ -344,12 +359,6 @@ refresh_matview_datafill(DestReceiver *dest, Query *query,
344359
FreeQueryDesc(queryDesc);
345360

346361
PopActiveSnapshot();
347-
348-
/* Roll back any GUC changes */
349-
AtEOXact_GUC(false, save_nestlevel);
350-
351-
/* Restore userid and security context */
352-
SetUserIdAndSecContext(save_userid, save_sec_context);
353362
}
354363

355364
DestReceiver *
@@ -520,7 +529,8 @@ mv_GenerateOper(StringInfo buf, Oid opoid)
520529
* this command.
521530
*/
522531
static void
523-
refresh_by_match_merge(Oid matviewOid, Oid tempOid)
532+
refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
533+
int save_sec_context)
524534
{
525535
StringInfoData querybuf;
526536
Relation matviewRel;
@@ -534,9 +544,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
534544
ListCell *indexoidscan;
535545
int16 relnatts;
536546
bool *usedForQual;
537-
Oid save_userid;
538-
int save_sec_context;
539-
int save_nestlevel;
540547

541548
initStringInfo(&querybuf);
542549
matviewRel = heap_open(matviewOid, NoLock);
@@ -587,6 +594,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
587594
SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1))));
588595
}
589596

597+
SetUserIdAndSecContext(relowner,
598+
save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
599+
590600
/* Start building the query for creating the diff table. */
591601
resetStringInfo(&querybuf);
592602
appendStringInfo(&querybuf,
@@ -681,9 +691,12 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
681691
if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
682692
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
683693

694+
SetUserIdAndSecContext(relowner,
695+
save_sec_context | SECURITY_RESTRICTED_OPERATION);
696+
684697
/*
685698
* We have no further use for data from the "full-data" temp table, but we
686-
* must keep it around because its type is reference from the diff table.
699+
* must keep it around because its type is referenced from the diff table.
687700
*/
688701

689702
/* Analyze the diff table. */
@@ -694,16 +707,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
694707

695708
OpenMatViewIncrementalMaintenance();
696709

697-
/*
698-
* Switch to the owner's userid, so that any functions are run as that
699-
* user. Also lock down security-restricted operations and arrange to
700-
* make GUC variable changes local to this command.
701-
*/
702-
GetUserIdAndSecContext(&save_userid, &save_sec_context);
703-
SetUserIdAndSecContext(matviewRel->rd_rel->relowner,
704-
save_sec_context | SECURITY_RESTRICTED_OPERATION);
705-
save_nestlevel = NewGUCNestLevel();
706-
707710
/* Deletes must come before inserts; do them first. */
708711
resetStringInfo(&querybuf);
709712
appendStringInfo(&querybuf,
@@ -724,12 +727,6 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid)
724727
if (SPI_exec(querybuf.data, 0) != SPI_OK_INSERT)
725728
elog(ERROR, "SPI_exec failed: %s", querybuf.data);
726729

727-
/* Roll back any GUC changes */
728-
AtEOXact_GUC(false, save_nestlevel);
729-
730-
/* Restore userid and security context */
731-
SetUserIdAndSecContext(save_userid, save_sec_context);
732-
733730
/* We're done maintaining the materialized view. */
734731
CloseMatViewIncrementalMaintenance();
735732
heap_close(tempRel, NoLock);

src/test/regress/expected/matview.out

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,3 +502,15 @@ SELECT * FROM mv_v;
502502

503503
DROP TABLE v CASCADE;
504504
NOTICE: drop cascades to materialized view mv_v
505+
-- make sure running as superuser works when MV owned by another role (bug #11208)
506+
CREATE ROLE user_dw;
507+
SET ROLE user_dw;
508+
CREATE TABLE foo_data AS SELECT i, md5(random()::text)
509+
FROM generate_series(1, 10) i;
510+
CREATE MATERIALIZED VIEW mv_foo AS SELECT * FROM foo_data;
511+
CREATE UNIQUE INDEX ON mv_foo (i);
512+
RESET ROLE;
513+
REFRESH MATERIALIZED VIEW mv_foo;
514+
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_foo;
515+
DROP OWNED BY user_dw CASCADE;
516+
DROP ROLE user_dw;

src/test/regress/sql/matview.sql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,16 @@ DELETE FROM v WHERE EXISTS ( SELECT * FROM mv_v WHERE mv_v.a = v.a );
194194
SELECT * FROM v;
195195
SELECT * FROM mv_v;
196196
DROP TABLE v CASCADE;
197+
198+
-- make sure running as superuser works when MV owned by another role (bug #11208)
199+
CREATE ROLE user_dw;
200+
SET ROLE user_dw;
201+
CREATE TABLE foo_data AS SELECT i, md5(random()::text)
202+
FROM generate_series(1, 10) i;
203+
CREATE MATERIALIZED VIEW mv_foo AS SELECT * FROM foo_data;
204+
CREATE UNIQUE INDEX ON mv_foo (i);
205+
RESET ROLE;
206+
REFRESH MATERIALIZED VIEW mv_foo;
207+
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_foo;
208+
DROP OWNED BY user_dw CASCADE;
209+
DROP ROLE user_dw;

0 commit comments

Comments
 (0)