mirror of
https://github.com/postgres/postgres.git
synced 2025-04-27 22:56:53 +03:00
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.
This commit is contained in:
parent
5569d75d6a
commit
a9d0f1cff3
@ -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,
|
|||||||
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,
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
|
|
||||||
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,
|
|||||||
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,
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
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)
|
|||||||
* 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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
|
|
||||||
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)
|
|||||||
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;
|
|||||||
|
|
||||||
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 );
|
|||||||
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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user