mirror of
https://github.com/postgres/postgres.git
synced 2025-07-14 08:21:07 +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:
@ -2151,6 +2151,15 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
|
||||
/* Likewise, copy the relids that are represented by this foreign scan */
|
||||
scan_plan->fs_relids = best_path->path.parent->relids;
|
||||
|
||||
/*
|
||||
* If a join between foreign relations was pushed down, remember it. The
|
||||
* push-down safety of the join depends upon the server and user mapping
|
||||
* being same. That can change between planning and execution time, in which
|
||||
* case the plan should be invalidated.
|
||||
*/
|
||||
if (scan_relid == 0)
|
||||
root->glob->hasForeignJoin = true;
|
||||
|
||||
/*
|
||||
* Replace any outer-relation variables with nestloop params in the qual,
|
||||
* fdw_exprs and fdw_recheck_quals expressions. We do this last so that
|
||||
|
@ -200,6 +200,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
|
||||
glob->lastPlanNodeId = 0;
|
||||
glob->transientPlan = false;
|
||||
glob->hasRowSecurity = false;
|
||||
glob->hasForeignJoin = false;
|
||||
|
||||
/*
|
||||
* Assess whether it's feasible to use parallel mode for this query. We
|
||||
@ -346,6 +347,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
|
||||
result->nParamExec = glob->nParamExec;
|
||||
result->hasRowSecurity = glob->hasRowSecurity;
|
||||
result->parallelModeNeeded = glob->parallelModeNeeded;
|
||||
result->hasForeignJoin = glob->hasForeignJoin;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -14,6 +14,9 @@
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "miscadmin.h"
|
||||
#include "catalog/pg_class.h"
|
||||
#include "foreign/foreign.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/cost.h"
|
||||
#include "optimizer/pathnode.h"
|
||||
@ -127,6 +130,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
|
||||
rel->subroot = NULL;
|
||||
rel->subplan_params = NIL;
|
||||
rel->serverid = InvalidOid;
|
||||
rel->umid = InvalidOid;
|
||||
rel->fdwroutine = NULL;
|
||||
rel->fdw_private = NULL;
|
||||
rel->baserestrictinfo = NIL;
|
||||
@ -166,6 +170,26 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
|
||||
break;
|
||||
}
|
||||
|
||||
/* For foreign tables get the user mapping */
|
||||
if (rte->relkind == RELKIND_FOREIGN_TABLE)
|
||||
{
|
||||
/*
|
||||
* This should match what ExecCheckRTEPerms() does.
|
||||
*
|
||||
* Note that if the plan ends up depending on the user OID in any
|
||||
* way - e.g. if it depends on the computed user mapping OID - we must
|
||||
* ensure that it gets invalidated in the case of a user OID change.
|
||||
* See RevalidateCachedQuery and more generally the hasForeignJoin
|
||||
* flags in PlannerGlobal and PlannedStmt.
|
||||
*/
|
||||
Oid userid;
|
||||
|
||||
userid = OidIsValid(rte->checkAsUser) ? rte->checkAsUser : GetUserId();
|
||||
rel->umid = GetUserMappingId(userid, rel->serverid);
|
||||
}
|
||||
else
|
||||
rel->umid = InvalidOid;
|
||||
|
||||
/* Save the finished struct in the query's simple_rel_array */
|
||||
root->simple_rel_array[relid] = rel;
|
||||
|
||||
@ -398,6 +422,7 @@ build_join_rel(PlannerInfo *root,
|
||||
joinrel->subroot = NULL;
|
||||
joinrel->subplan_params = NIL;
|
||||
joinrel->serverid = InvalidOid;
|
||||
joinrel->umid = InvalidOid;
|
||||
joinrel->fdwroutine = NULL;
|
||||
joinrel->fdw_private = NULL;
|
||||
joinrel->baserestrictinfo = NIL;
|
||||
@ -408,12 +433,19 @@ build_join_rel(PlannerInfo *root,
|
||||
|
||||
/*
|
||||
* Set up foreign-join fields if outer and inner relation are foreign
|
||||
* tables (or joins) belonging to the same server.
|
||||
* tables (or joins) belonging to the same server and using the same
|
||||
* user mapping.
|
||||
*
|
||||
* Otherwise those fields are left invalid, so FDW API will not be called
|
||||
* for the join relation.
|
||||
*/
|
||||
if (OidIsValid(outer_rel->serverid) &&
|
||||
inner_rel->serverid == outer_rel->serverid)
|
||||
inner_rel->serverid == outer_rel->serverid &&
|
||||
inner_rel->umid == outer_rel->umid)
|
||||
{
|
||||
Assert(OidIsValid(outer_rel->umid));
|
||||
joinrel->serverid = outer_rel->serverid;
|
||||
joinrel->umid = outer_rel->umid;
|
||||
joinrel->fdwroutine = outer_rel->fdwroutine;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user