mirror of
https://github.com/postgres/postgres.git
synced 2025-07-02 09:02:37 +03:00
Only try to push down foreign joins if the user mapping OIDs match.
Previously, the foreign join pushdown infrastructure left the question of security entirely up to individual FDWs, but it would be easy for a foreign data wrapper to inadvertently open up subtle security holes that way. So, make it the core code's job to determine which user mapping OID is relevant, and don't attempt join pushdown unless it's the same for all relevant relations. Per a suggestion from Tom Lane. Shigeru Hanada and Ashutosh Bapat, reviewed by Etsuro Fujita and KaiGai Kohei, with some further changes by me.
This commit is contained in:
68
src/backend/utils/cache/plancache.c
vendored
68
src/backend/utils/cache/plancache.c
vendored
@ -104,6 +104,8 @@ static TupleDesc PlanCacheComputeResultDesc(List *stmt_list);
|
||||
static void PlanCacheRelCallback(Datum arg, Oid relid);
|
||||
static void PlanCacheFuncCallback(Datum arg, int cacheid, uint32 hashvalue);
|
||||
static void PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue);
|
||||
static void PlanCacheUserMappingCallback(Datum arg, int cacheid,
|
||||
uint32 hashvalue);
|
||||
|
||||
|
||||
/*
|
||||
@ -119,6 +121,8 @@ InitPlanCache(void)
|
||||
CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheSysCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(OPEROID, PlanCacheSysCallback, (Datum) 0);
|
||||
CacheRegisterSyscacheCallback(AMOPOPID, PlanCacheSysCallback, (Datum) 0);
|
||||
/* User mapping change may invalidate plans with pushed down foreign join */
|
||||
CacheRegisterSyscacheCallback(USERMAPPINGOID, PlanCacheUserMappingCallback, (Datum) 0);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -574,7 +578,8 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
|
||||
/*
|
||||
* If this is a new cached plan, then set the user id it was planned by
|
||||
* and under what row security settings; these are needed to determine
|
||||
* plan invalidation when RLS is involved.
|
||||
* plan invalidation when RLS is involved or foreign joins are pushed
|
||||
* down.
|
||||
*/
|
||||
if (!OidIsValid(plansource->planUserId))
|
||||
{
|
||||
@ -609,6 +614,18 @@ RevalidateCachedQuery(CachedPlanSource *plansource)
|
||||
|| plansource->row_security_env != row_security))
|
||||
plansource->is_valid = false;
|
||||
|
||||
/*
|
||||
* If we have a join pushed down to the foreign server and the current user
|
||||
* is different from the one for which the plan was created, invalidate the
|
||||
* generic plan since user mapping for the new user might make the join
|
||||
* unsafe to push down, or change which user mapping is used.
|
||||
*/
|
||||
if (plansource->is_valid &&
|
||||
plansource->gplan &&
|
||||
plansource->gplan->has_foreign_join &&
|
||||
plansource->planUserId != GetUserId())
|
||||
plansource->gplan->is_valid = false;
|
||||
|
||||
/*
|
||||
* If the query is currently valid, acquire locks on the referenced
|
||||
* objects; then check again. We need to do it this way to cover the race
|
||||
@ -881,6 +898,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
|
||||
bool spi_pushed;
|
||||
MemoryContext plan_context;
|
||||
MemoryContext oldcxt = CurrentMemoryContext;
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* Normally the querytree should be valid already, but if it's not,
|
||||
@ -988,6 +1006,20 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
|
||||
plan->is_saved = false;
|
||||
plan->is_valid = true;
|
||||
|
||||
/*
|
||||
* Walk through the plist and set hasForeignJoin if any of the plans have
|
||||
* it set.
|
||||
*/
|
||||
plan->has_foreign_join = false;
|
||||
foreach(lc, plist)
|
||||
{
|
||||
PlannedStmt *plan_stmt = (PlannedStmt *) lfirst(lc);
|
||||
|
||||
if (IsA(plan_stmt, PlannedStmt))
|
||||
plan->has_foreign_join =
|
||||
plan->has_foreign_join || plan_stmt->hasForeignJoin;
|
||||
}
|
||||
|
||||
/* assign generation number to new plan */
|
||||
plan->generation = ++(plansource->generation);
|
||||
|
||||
@ -1843,6 +1875,40 @@ PlanCacheSysCallback(Datum arg, int cacheid, uint32 hashvalue)
|
||||
ResetPlanCache();
|
||||
}
|
||||
|
||||
/*
|
||||
* PlanCacheUserMappingCallback
|
||||
* Syscache inval callback function for user mapping cache invalidation.
|
||||
*
|
||||
* Invalidates plans which have pushed down foreign joins.
|
||||
*/
|
||||
static void
|
||||
PlanCacheUserMappingCallback(Datum arg, int cacheid, uint32 hashvalue)
|
||||
{
|
||||
CachedPlanSource *plansource;
|
||||
|
||||
for (plansource = first_saved_plan; plansource; plansource = plansource->next_saved)
|
||||
{
|
||||
Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC);
|
||||
|
||||
/* No work if it's already invalidated */
|
||||
if (!plansource->is_valid)
|
||||
continue;
|
||||
|
||||
/* Never invalidate transaction control commands */
|
||||
if (IsTransactionStmtPlan(plansource))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* If the plan has pushed down foreign joins, those join may become
|
||||
* unsafe to push down because of user mapping changes. Invalidate only
|
||||
* the generic plan, since changes to user mapping do not invalidate the
|
||||
* parse tree.
|
||||
*/
|
||||
if (plansource->gplan && plansource->gplan->has_foreign_join)
|
||||
plansource->gplan->is_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ResetPlanCache: invalidate all cached plans.
|
||||
*/
|
||||
|
Reference in New Issue
Block a user