pgsql-fix-leaky-join-view.3.patch
text/x-patch
Filename: pgsql-fix-leaky-join-view.3.patch
Type: text/x-patch
Part: 0
Message:
Re: [PATCH] Fix leaky VIEWs for RLS
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
| File | + | − |
|---|---|---|
| src/backend/optimizer/plan/initsplan.c | 94 | 0 |
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 45,57 **** static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
Relids inner_join_rels,
JoinType jointype, List *clause);
! static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_deduced,
bool below_outer_join,
JoinType jointype,
Relids qualscope,
Relids ojscope,
Relids outerjoin_nonnullable);
static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
Relids *nullable_relids_p, bool is_pushed_down);
static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
--- 45,60 ----
Relids left_rels, Relids right_rels,
Relids inner_join_rels,
JoinType jointype, List *clause);
! static void distribute_qual_to_rels(PlannerInfo *root,
! Node *clause,
! Node *jtnode,
bool is_deduced,
bool below_outer_join,
JoinType jointype,
Relids qualscope,
Relids ojscope,
Relids outerjoin_nonnullable);
+ static Relids prevent_untrusted_pushdown(Node *jtnode, Relids relids, bool recursing);
static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
Relids *nullable_relids_p, bool is_pushed_down);
static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
***************
*** 334,340 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
{
Node *qual = (Node *) lfirst(l);
! distribute_qual_to_rels(root, qual,
false, below_outer_join, JOIN_INNER,
*qualscope, NULL, NULL);
}
--- 337,343 ----
{
Node *qual = (Node *) lfirst(l);
! distribute_qual_to_rels(root, qual, jtnode,
false, below_outer_join, JOIN_INNER,
*qualscope, NULL, NULL);
}
***************
*** 457,463 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
{
Node *qual = (Node *) lfirst(l);
! distribute_qual_to_rels(root, qual,
false, below_outer_join, j->jointype,
*qualscope,
ojscope, nonnullable_rels);
--- 460,466 ----
{
Node *qual = (Node *) lfirst(l);
! distribute_qual_to_rels(root, qual, jtnode,
false, below_outer_join, j->jointype,
*qualscope,
ojscope, nonnullable_rels);
***************
*** 728,733 **** make_outerjoininfo(PlannerInfo *root,
--- 731,737 ----
* EquivalenceClasses.
*
* 'clause': the qual clause to be distributed
+ * 'jtnode': the join tree node where the 'clause' was chained in original.
* 'is_deduced': TRUE if the qual came from implied-equality deduction
* 'below_outer_join': TRUE if the qual is from a JOIN/ON that is below the
* nullable side of a higher-level outer join
***************
*** 748,754 **** make_outerjoininfo(PlannerInfo *root,
* all and only those special joins that are syntactically below this qual.
*/
static void
! distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool is_deduced,
bool below_outer_join,
JoinType jointype,
--- 752,760 ----
* all and only those special joins that are syntactically below this qual.
*/
static void
! distribute_qual_to_rels(PlannerInfo *root,
! Node *clause,
! Node *jtnode,
bool is_deduced,
bool below_outer_join,
JoinType jointype,
***************
*** 771,776 **** distribute_qual_to_rels(PlannerInfo *root, Node *clause,
--- 777,807 ----
relids = pull_varnos(clause);
/*
+ * Prevent untrusted function calls to be pushed down to inside of
+ * subqueries already pulled up; which might be views.
+ * When a view is used to row-level security purpose, it may have
+ * WHERE clause to filter out invisible tuples. However, functions
+ * with side-effect allow us to leak contents of invisible tuples,
+ * if user given qualifiers are evaluated prior to WHERE clause of
+ * views.
+ * For example, when a view contains A JOIN B and user's qualifier
+ * takes arguments that reference columns of A, it prefers to be
+ * distributed on scan plan of A. But it also means the qualifier
+ * may be launched with arguments come from invisible tuples.
+ * In this case, the contents of invisible tuples may be leaked
+ * via user's qualifier which has side-effects, such as inserting
+ * the arguments into other tables.
+ * Hence, it needs to prevent untrusted function calls to be pushed
+ * down to inside of subqueries.
+ *
+ * XXX - To minimize performance impact, it should be prevented
+ * only when the clause contains 'untrusted' function calls.
+ * But the algorithm to determine whether a certain function is
+ * trusted, or not, is under discussion.
+ */
+ relids = prevent_untrusted_pushdown(jtnode, relids, false);
+
+ /*
* Cross-check: clause should contain no relids not within its scope.
* Otherwise the parser messed up.
*/
***************
*** 1075,1080 **** distribute_qual_to_rels(PlannerInfo *root, Node *clause,
--- 1106,1169 ----
}
/*
+ * prevent_untrusted_pushdown
+ *
+ * It expands the given relids when it tries to reference a part of
+ * subqueries, to prevent unexpected pushing down due to the security
+ * reason.
+ */
+ static Relids
+ prevent_untrusted_pushdown(Node *jtnode, Relids relids, bool recursing)
+ {
+ if (!jtnode || IsA(jtnode, RangeTblRef))
+ return relids;
+
+ if (IsA(jtnode, FromExpr))
+ {
+ FromExpr *f = (FromExpr *) jtnode;
+ ListCell *l;
+ Relids temp;
+
+ if (!recursing)
+ {
+ /*
+ * The FromExpr in the top-level does not mean subqueries.
+ * It is same level of the clause originally chained.
+ */
+ foreach (l, f->fromlist)
+ relids = prevent_untrusted_pushdown(lfirst(l), relids, true);
+ }
+ else
+ {
+ /*
+ * If we find out FromExpr except for the top-level join tree,
+ * it means subqueries pulled up on the earlier stage.
+ * In the case when the given relids references a part of
+ * relations within the FromExpr, it shall be expanded to
+ * whole of the subquery to prevent unexpected pushing-down.
+ */
+ temp = get_relids_in_jointree(jtnode, false);
+
+ if (bms_overlap(relids, temp))
+ relids = bms_add_members(relids, temp);
+
+ bms_free(temp);
+ }
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+
+ relids = prevent_untrusted_pushdown(j->larg, relids, true);
+ relids = prevent_untrusted_pushdown(j->rarg, relids, true);
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode));
+
+ return relids;
+ }
+
+ /*
* check_outerjoin_delay
* Detect whether a qual referencing the given relids must be delayed
* in application due to the presence of a lower outer join, and/or
***************
*** 1357,1363 **** process_implied_equality(PlannerInfo *root,
/*
* Push the new clause into all the appropriate restrictinfo lists.
*/
! distribute_qual_to_rels(root, (Node *) clause,
true, below_outer_join, JOIN_INNER,
qualscope, NULL, NULL);
}
--- 1446,1452 ----
/*
* Push the new clause into all the appropriate restrictinfo lists.
*/
! distribute_qual_to_rels(root, (Node *) clause, NULL,
true, below_outer_join, JOIN_INNER,
qualscope, NULL, NULL);
}