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);
  }