On 7 April 2015 at 16:21, Stephen Frost <sfr...@snowman.net> wrote:
> Agreed and we actually have a patch from Dean already to address this,
> it's just been waiting on me (with a couple of other ones).  It'd
> certainly be great if you have time to take a look at those, though,
> generally speaking, I feel prety happy about where those are and believe
> they really just need to be reviewed/tested and maybe a bit of word-
> smithing around the docs.
>

The first of those patches [1] has bit-rotted somewhat, due to the
recent changes to the way rowmarks are handled, so here's an updated
version. It wasn't a trivial merge, because commit
cb1ca4d800621dcae67ca6c799006de99fa4f0a5 made a change to
ExecBuildAuxRowMark() without a matching change to
preprocess_targetlist(), and one of the new RLS-with-inheritance tests
fell over that.

This is not a complete review of RLS, but it does fix a number of issues:

1). In prepend_row_security_policies(), defaultDeny was always true,
so if there were any hook policies, the RLS policies on the table
would just get discarded.

2). In prepend_row_security_policies(), I think it is better to have
any table RLS policies applied before any hook policies, so that a
hook cannot be used to bypass built-in RLS.

3). The infinite recursion detection in fireRIRrules() didn't properly
manage the activeRIRs list in the case of WCOs, so it would
incorrectly report infinite recusion if the same relation with RLS
appeared more than once in the rtable, for example "UPDATE t ... FROM
t ...".

4). The RLS expansion code in fireRIRrules() was handling RLS in the
main loop through the rtable. This lead to RTEs being visited twice if
they contained sublink subqueries, which
prepend_row_security_policies() attempted to handle by exiting early
if the RTE already had securityQuals. That didn't work, however, since
if the query involved a security barrier view on top of a table with
RLS, the RTE would already have securityQuals (from the view) by the
time fireRIRrules() was invoked, and so the table's RLS policies would
be ignored. This is most easily fixed in fireRIRrules() by handling
RLS in a separate loop at the end, after dealing with any other
sublink subqueries, thus ensuring that each RTE is only visited once
for RLS expansion.

5). The inheritance planner code didn't correctly handle non-target
relations with RLS, which would get turned into subqueries during
planning. Thus an update of the form "UPDATE t1 ... FROM t2 ..." where
t1 has inheritance and t2 has RLS quals would fail.

6). process_policies() was adding WCOs to non-target relations, which
is unnecessary, and could lead to a lot of wasted time in the rewriter
and the planner. WCOs are only needed on the result relation.


The second patch [2] is the one that is actually relevant to this
thread. This patch is primarily to apply the RLS checks earlier,
before an update is attempted, more like a regular permissions check.
This adds a new enum to classify the kinds of WCO, a side benefit of
which is that it allows different error messages when RLS checks are
violated, as opposed to WITH CHECK OPTIONs on views.

I actually re-used the sql status code 42501 -
ERRCODE_INSUFFICIENT_PRIVILEGE for a RLS check failure because of the
parallel with permissions checks, but I quite like Craig's idea of
inventing a new status code for this, so that it can be more easily
distinguished from a lack of GRANTed privileges.

There's another side benefit to this patch, which is that the new enum
could be extended to include a new kind of WCO for a check of the
USING quals of a RLS UPDATE policy on the update path of an INSERT..ON
CONFLICT UPDATE. As I pointed out on that thread, I think these quals
need to be treated differently from the WITH CHECK quals of a RLS
UPDATE policy, since they ought to apply to different tuples.
Therefore classifying the WCOs by command type is insufficient to
distinguish the 2 cases.

Regards,
Dean


[1] 
http://www.postgresql.org/message-id/caezatcvoaniy5qlgda4k-nmp7gbjjgpvgucbata1ckvm-uw...@mail.gmail.com

[2] 
http://www.postgresql.org/message-id/caezatcvz1u3dyjdzxn3f26rxks2byxdz--_2rtzfrhuu0zf...@mail.gmail.com
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 876a87f..ea4d4c5
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** inheritance_planner(PlannerInfo *root)
*** 790,795 ****
--- 790,796 ----
  {
  	Query	   *parse = root->parse;
  	int			parentRTindex = parse->resultRelation;
+ 	Bitmapset  *resultRTindexes = NULL;
  	int			nominalRelation = -1;
  	List	   *final_rtable = NIL;
  	int			save_rel_array_size = 0;
*************** inheritance_planner(PlannerInfo *root)
*** 815,821 ****
--- 816,836 ----
  	 * (1) would result in a rangetable of length O(N^2) for N targets, with
  	 * at least O(N^3) work expended here; and (2) would greatly complicate
  	 * management of the rowMarks list.
+ 	 *
+ 	 * Note that any RTEs with security barrier quals will be turned into
+ 	 * subqueries during planning, and so we must create copies of them too,
+ 	 * except where they are target relations, which will each only be used
+ 	 * in a single plan.
  	 */
+ 	resultRTindexes = bms_add_member(resultRTindexes, parentRTindex);
+ 	foreach(lc, root->append_rel_list)
+ 	{
+ 		AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
+ 		if (appinfo->parent_relid == parentRTindex)
+ 			resultRTindexes = bms_add_member(resultRTindexes,
+ 											 appinfo->child_relid);
+ 	}
+ 
  	foreach(lc, root->append_rel_list)
  	{
  		AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
*************** inheritance_planner(PlannerInfo *root)
*** 886,906 ****
  			{
  				RangeTblEntry *rte = (RangeTblEntry *) lfirst(lr);
  
! 				if (rte->rtekind == RTE_SUBQUERY)
  				{
  					Index		newrti;
  
  					/*
  					 * The RTE can't contain any references to its own RT
! 					 * index, so we can save a few cycles by applying
! 					 * ChangeVarNodes before we append the RTE to the
! 					 * rangetable.
  					 */
  					newrti = list_length(subroot.parse->rtable) + 1;
  					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
  					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
  					ChangeVarNodes((Node *) subroot.append_rel_list, rti, newrti, 0);
  					rte = copyObject(rte);
  					subroot.parse->rtable = lappend(subroot.parse->rtable,
  													rte);
  				}
--- 901,929 ----
  			{
  				RangeTblEntry *rte = (RangeTblEntry *) lfirst(lr);
  
! 				/*
! 				 * Copy subquery RTEs and RTEs with security barrier quals
! 				 * that will be turned into subqueries, except those that are
! 				 * target relations.
! 				 */
! 				if (rte->rtekind == RTE_SUBQUERY ||
! 					(rte->securityQuals != NIL &&
! 					 !bms_is_member(rti, resultRTindexes)))
  				{
  					Index		newrti;
  
  					/*
  					 * The RTE can't contain any references to its own RT
! 					 * index, except in the security barrier quals, so we can
! 					 * save a few cycles by applying ChangeVarNodes before we
! 					 * append the RTE to the rangetable.
  					 */
  					newrti = list_length(subroot.parse->rtable) + 1;
  					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
  					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
  					ChangeVarNodes((Node *) subroot.append_rel_list, rti, newrti, 0);
  					rte = copyObject(rte);
+ 					ChangeVarNodes((Node *) rte->securityQuals, rti, newrti, 0);
  					subroot.parse->rtable = lappend(subroot.parse->rtable,
  													rte);
  				}
*************** select_rowmark_type(RangeTblEntry *rte,
*** 2283,2289 ****
  		switch (strength)
  		{
  			case LCS_NONE:
! 				/* don't need tuple lock, only ability to re-fetch the row */
  				return ROW_MARK_REFERENCE;
  				break;
  			case LCS_FORKEYSHARE:
--- 2306,2324 ----
  		switch (strength)
  		{
  			case LCS_NONE:
! 				/*
! 				 * We don't need a tuple lock, only the ability to re-fetch
! 				 * the row.  Regular tables support ROW_MARK_REFERENCE, but if
! 				 * this RTE has security barrier quals, it will be turned into
! 				 * a subquery during planning, so use ROW_MARK_COPY.
! 				 *
! 				 * This is only necessary for LCS_NONE, since real tuple locks
! 				 * on an RTE with security barrier quals are supported by
! 				 * pushing the lock down into the subquery --- see
! 				 * expand_security_qual.
! 				 */
! 				if (rte->securityQuals != NIL)
! 					return ROW_MARK_COPY;
  				return ROW_MARK_REFERENCE;
  				break;
  			case LCS_FORKEYSHARE:
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
new file mode 100644
index 08e7c44..779befd
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
*************** preprocess_targetlist(PlannerInfo *root,
*** 107,129 ****
  								  pstrdup(resname),
  								  true);
  			tlist = lappend(tlist, tle);
- 
- 			/* if parent of inheritance tree, need the tableoid too */
- 			if (rc->isParent)
- 			{
- 				var = makeVar(rc->rti,
- 							  TableOidAttributeNumber,
- 							  OIDOID,
- 							  -1,
- 							  InvalidOid,
- 							  0);
- 				snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId);
- 				tle = makeTargetEntry((Expr *) var,
- 									  list_length(tlist) + 1,
- 									  pstrdup(resname),
- 									  true);
- 				tlist = lappend(tlist, tle);
- 			}
  		}
  		if (rc->allMarkTypes & (1 << ROW_MARK_COPY))
  		{
--- 107,112 ----
*************** preprocess_targetlist(PlannerInfo *root,
*** 136,141 ****
--- 119,141 ----
  			tle = makeTargetEntry((Expr *) var,
  								  list_length(tlist) + 1,
  								  pstrdup(resname),
+ 								  true);
+ 			tlist = lappend(tlist, tle);
+ 		}
+ 
+ 		/* If parent of inheritance tree, need to fetch the tableoid */
+ 		if (rc->isParent)
+ 		{
+ 			var = makeVar(rc->rti,
+ 						  TableOidAttributeNumber,
+ 						  OIDOID,
+ 						  -1,
+ 						  InvalidOid,
+ 						  0);
+ 			snprintf(resname, sizeof(resname), "tableoid%u", rc->rowmarkId);
+ 			tle = makeTargetEntry((Expr *) var,
+ 								  list_length(tlist) + 1,
+ 								  pstrdup(resname),
  								  true);
  			tlist = lappend(tlist, tle);
  		}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 9d2c280..08ec13c
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** fireRIRrules(Query *parsetree, List *act
*** 1714,1764 ****
  				activeRIRs = list_delete_first(activeRIRs);
  			}
  		}
- 		/*
- 		 * If the RTE has row security quals, apply them and recurse into the
- 		 * securityQuals.
- 		 */
- 		if (prepend_row_security_policies(parsetree, rte, rt_index))
- 		{
- 			/*
- 			 * We applied security quals, check for infinite recursion and
- 			 * then expand any nested queries.
- 			 */
- 			if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
- 					ereport(ERROR,
- 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- 							 errmsg("infinite recursion detected in policy for relation \"%s\"",
- 									RelationGetRelationName(rel))));
- 
- 			/*
- 			 * Make sure we check for recursion in either securityQuals or
- 			 * WITH CHECK quals.
- 			 */
- 			if (rte->securityQuals != NIL)
- 			{
- 				activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
- 
- 				expression_tree_walker( (Node*) rte->securityQuals,
- 										fireRIRonSubLink, (void*)activeRIRs );
- 
- 				activeRIRs = list_delete_first(activeRIRs);
- 			}
- 
- 			if (parsetree->withCheckOptions != NIL)
- 			{
- 				WithCheckOption    *wco;
- 				List			   *quals = NIL;
- 
- 				wco = (WithCheckOption *) makeNode(WithCheckOption);
- 				quals = lcons(wco->qual, quals);
- 
- 				activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
- 
- 				expression_tree_walker( (Node*) quals, fireRIRonSubLink,
- 									   (void*)activeRIRs);
- 			}
- 
- 		}
  
  		heap_close(rel, NoLock);
  	}
--- 1714,1719 ----
*************** fireRIRrules(Query *parsetree, List *act
*** 1780,1785 ****
--- 1735,1822 ----
  		query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs,
  						  QTW_IGNORE_RC_SUBQUERIES);
  
+ 	/*
+ 	 * Apply any row level security policies.  We do this last because it
+ 	 * requires special recursion detection if the new quals have sublink
+ 	 * subqueries, and if we did it in the loop above query_tree_walker
+ 	 * would then recurse into those quals a second time.
+ 	 */
+ 	rt_index = 0;
+ 	foreach(lc, parsetree->rtable)
+ 	{
+ 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+ 		Relation	rel;
+ 		List	   *securityQuals;
+ 		List	   *withCheckOptions;
+ 		bool		hasRowSecurity;
+ 		bool		hasSubLinks;
+ 
+ 		++rt_index;
+ 
+ 		/* Only normal relations can have RLS policies */
+ 		if (rte->rtekind != RTE_RELATION ||
+ 			rte->relkind != RELKIND_RELATION)
+ 			continue;
+ 
+ 		rel = heap_open(rte->relid, NoLock);
+ 
+ 		/*
+ 		 * Fetch any new security quals that must be applied to this RTE.
+ 		 */
+ 		get_row_security_policies(parsetree, rte, rt_index,
+ 								  &securityQuals, &withCheckOptions,
+ 								  &hasRowSecurity, &hasSubLinks);
+ 
+ 		if (securityQuals != NIL || withCheckOptions != NIL)
+ 		{
+ 			if (hasSubLinks)
+ 			{
+ 				/*
+ 				 * Recursively process the new quals, checking for infinite
+ 				 * recursion.
+ 				 */
+ 				if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 							 errmsg("infinite recursion detected in policy for relation \"%s\"",
+ 									RelationGetRelationName(rel))));
+ 
+ 				activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
+ 
+ 				expression_tree_walker( (Node*) securityQuals,
+ 										fireRIRonSubLink, (void*)activeRIRs );
+ 
+ 				expression_tree_walker( (Node*) withCheckOptions,
+ 										fireRIRonSubLink, (void*)activeRIRs );
+ 
+ 				activeRIRs = list_delete_first(activeRIRs);
+ 			}
+ 
+ 			/*
+ 			 * Add the new security quals to the start of the RTE's list so
+ 			 * that they get applied before any existing security quals (which
+ 			 * might have come from a user-written security barrier view, and
+ 			 * might contain malicious code).
+ 			 */
+ 			rte->securityQuals = list_concat(securityQuals,
+ 											 rte->securityQuals);
+ 
+ 			parsetree->withCheckOptions = list_concat(withCheckOptions,
+ 													  parsetree->withCheckOptions);
+ 		}
+ 
+ 		/*
+ 		 * Make sure the query is marked correctly if row level security
+ 		 * applies, or if the new quals had sublinks.
+ 		 */
+ 		if (hasRowSecurity)
+ 			parsetree->hasRowSecurity = true;
+ 		if (hasSubLinks)
+ 			parsetree->hasSubLinks = true;
+ 
+ 		heap_close(rel, NoLock);
+ 	}
+ 
  	return parsetree;
  }
  
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
new file mode 100644
index 7669130..73a5794
*** a/src/backend/rewrite/rowsecurity.c
--- b/src/backend/rewrite/rowsecurity.c
***************
*** 58,64 ****
  
  static List *pull_row_security_policies(CmdType cmd, Relation relation,
  										Oid user_id);
! static void process_policies(List *policies, int rt_index,
  							 Expr **final_qual,
  							 Expr **final_with_check_qual,
  							 bool *hassublinks);
--- 58,64 ----
  
  static List *pull_row_security_policies(CmdType cmd, Relation relation,
  										Oid user_id);
! static void process_policies(Query* root, List *policies, int rt_index,
  							 Expr **final_qual,
  							 Expr **final_with_check_qual,
  							 bool *hassublinks);
*************** static bool check_role_for_policy(ArrayT
*** 73,88 ****
  row_security_policy_hook_type	row_security_policy_hook = NULL;
  
  /*
!  * Check the given RTE to see whether it's already had row security quals
!  * expanded and, if not, prepend any row security rules from built-in or
!  * plug-in sources to the securityQuals. The security quals are rewritten (for
!  * view expansion, etc) before being added to the RTE.
   *
!  * Returns true if any quals were added. Note that quals may have been found
!  * but not added if user rights make the user exempt from row security.
   */
! bool
! prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
  {
  	Expr			   *rowsec_expr = NULL;
  	Expr			   *rowsec_with_check_expr = NULL;
--- 73,89 ----
  row_security_policy_hook_type	row_security_policy_hook = NULL;
  
  /*
!  * Get any row security quals and check quals that should be applied to the
!  * specified RTE.
   *
!  * In addition hasRowSecurity is set to true if row level security is enabled
!  * (even if this RTE doesn't have any row security quals), and hasSubLinks is
!  * set to true if any of the quals returned contain sublinks.
   */
! void
! get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
! 						  List **securityQuals, List **withCheckOptions,
! 						  bool *hasRowSecurity, bool *hasSubLinks)
  {
  	Expr			   *rowsec_expr = NULL;
  	Expr			   *rowsec_with_check_expr = NULL;
*************** prepend_row_security_policies(Query* roo
*** 96,103 ****
  	Oid					user_id;
  	int					sec_context;
  	int					rls_status;
! 	bool				defaultDeny = true;
! 	bool				hassublinks = false;
  
  	/* This is just to get the security context */
  	GetUserIdAndSecContext(&user_id, &sec_context);
--- 97,109 ----
  	Oid					user_id;
  	int					sec_context;
  	int					rls_status;
! 	bool				defaultDeny = false;
! 
! 	/* Defaults for the return values */
! 	*securityQuals = NIL;
! 	*withCheckOptions = NIL;
! 	*hasRowSecurity = false;
! 	*hasSubLinks = false;
  
  	/* This is just to get the security context */
  	GetUserIdAndSecContext(&user_id, &sec_context);
*************** prepend_row_security_policies(Query* roo
*** 113,126 ****
  	if (rte->relid < FirstNormalObjectId
  		|| rte->relkind != RELKIND_RELATION
  		|| (sec_context & SECURITY_ROW_LEVEL_DISABLED))
! 		return false;
  
  	/* Determine the state of RLS for this, pass checkAsUser explicitly */
  	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
  
  	/* If there is no RLS on this table at all, nothing to do */
  	if (rls_status == RLS_NONE)
! 		return false;
  
  	/*
  	 * RLS_NONE_ENV means we are not doing any RLS now, but that may change
--- 119,132 ----
  	if (rte->relid < FirstNormalObjectId
  		|| rte->relkind != RELKIND_RELATION
  		|| (sec_context & SECURITY_ROW_LEVEL_DISABLED))
! 		return;
  
  	/* Determine the state of RLS for this, pass checkAsUser explicitly */
  	rls_status = check_enable_rls(rte->relid, rte->checkAsUser, false);
  
  	/* If there is no RLS on this table at all, nothing to do */
  	if (rls_status == RLS_NONE)
! 		return;
  
  	/*
  	 * RLS_NONE_ENV means we are not doing any RLS now, but that may change
*************** prepend_row_security_policies(Query* roo
*** 134,151 ****
  		 * be replanned if the environment changes (GUCs, role), but we
  		 * are not adding anything here.
  		 */
! 		root->hasRowSecurity = true;
  
! 		return false;
  	}
  
- 	/*
- 	 * We may end up getting called multiple times for the same RTE, so check
- 	 * to make sure we aren't doing double-work.
- 	 */
- 	if (rte->securityQuals != NIL)
- 		return false;
- 
  	/* Grab the built-in policies which should be applied to this relation. */
  	rel = heap_open(rte->relid, NoLock);
  
--- 140,150 ----
  		 * be replanned if the environment changes (GUCs, role), but we
  		 * are not adding anything here.
  		 */
! 		*hasRowSecurity = true;
  
! 		return;
  	}
  
  	/* Grab the built-in policies which should be applied to this relation. */
  	rel = heap_open(rte->relid, NoLock);
  
*************** prepend_row_security_policies(Query* roo
*** 167,174 ****
  		defaultDeny = true;
  
  	/* Now that we have our policies, build the expressions from them. */
! 	process_policies(rowsec_policies, rt_index, &rowsec_expr,
! 					 &rowsec_with_check_expr, &hassublinks);
  
  	/*
  	 * Also, allow extensions to add their own policies.
--- 166,173 ----
  		defaultDeny = true;
  
  	/* Now that we have our policies, build the expressions from them. */
! 	process_policies(root, rowsec_policies, rt_index, &rowsec_expr,
! 					 &rowsec_with_check_expr, hasSubLinks);
  
  	/*
  	 * Also, allow extensions to add their own policies.
*************** prepend_row_security_policies(Query* roo
*** 191,204 ****
  	 * enabled on the table, then we will ignore the internally-generated
  	 * default-deny policy and use only the policies returned by the
  	 * extension.
  	 */
  	if (row_security_policy_hook)
  	{
  		hook_policies = (*row_security_policy_hook)(root->commandType, rel);
  
  		/* Build the expression from any policies returned. */
! 		process_policies(hook_policies, rt_index, &hook_expr,
! 						 &hook_with_check_expr, &hassublinks);
  	}
  
  	/*
--- 190,206 ----
  	 * enabled on the table, then we will ignore the internally-generated
  	 * default-deny policy and use only the policies returned by the
  	 * extension.
+ 	 *
+ 	 * Any extension policies are applied after any internally-generated
+ 	 * policies, so that extensions cannot bypass built-in RLS.
  	 */
  	if (row_security_policy_hook)
  	{
  		hook_policies = (*row_security_policy_hook)(root->commandType, rel);
  
  		/* Build the expression from any policies returned. */
! 		process_policies(root, hook_policies, rt_index, &hook_expr,
! 						 &hook_with_check_expr, hasSubLinks);
  	}
  
  	/*
*************** prepend_row_security_policies(Query* roo
*** 230,236 ****
  			wco->viewname = RelationGetRelationName(rel);
  			wco->qual = (Node *) rowsec_with_check_expr;
  			wco->cascaded = false;
! 			root->withCheckOptions = lcons(wco, root->withCheckOptions);
  		}
  
  		/*
--- 232,238 ----
  			wco->viewname = RelationGetRelationName(rel);
  			wco->qual = (Node *) rowsec_with_check_expr;
  			wco->cascaded = false;
! 			*withCheckOptions = lappend(*withCheckOptions, wco);
  		}
  
  		/*
*************** prepend_row_security_policies(Query* roo
*** 244,250 ****
  			wco->viewname = RelationGetRelationName(rel);
  			wco->qual = (Node *) hook_with_check_expr;
  			wco->cascaded = false;
! 			root->withCheckOptions = lcons(wco, root->withCheckOptions);
  		}
  	}
  
--- 246,252 ----
  			wco->viewname = RelationGetRelationName(rel);
  			wco->qual = (Node *) hook_with_check_expr;
  			wco->cascaded = false;
! 			*withCheckOptions = lappend(*withCheckOptions, wco);
  		}
  	}
  
*************** prepend_row_security_policies(Query* roo
*** 254,264 ****
  		|| root->commandType == CMD_DELETE)
  	{
  		if (rowsec_expr)
! 			rte->securityQuals = lcons(rowsec_expr, rte->securityQuals);
  
  		if (hook_expr)
! 			rte->securityQuals = lcons(hook_expr,
! 									   rte->securityQuals);
  	}
  
  	heap_close(rel, NoLock);
--- 256,265 ----
  		|| root->commandType == CMD_DELETE)
  	{
  		if (rowsec_expr)
! 			*securityQuals = lappend(*securityQuals, rowsec_expr);
  
  		if (hook_expr)
! 			*securityQuals = lappend(*securityQuals, hook_expr);
  	}
  
  	heap_close(rel, NoLock);
*************** prepend_row_security_policies(Query* roo
*** 267,283 ****
  	 * Mark this query as having row security, so plancache can invalidate
  	 * it when necessary (eg: role changes)
  	 */
! 	root->hasRowSecurity = true;
! 
! 	/*
! 	 * If we have sublinks added because of the policies being added to the
! 	 * query, then set hasSubLinks on the Query to force subLinks to be
! 	 * properly expanded.
! 	 */
! 	root->hasSubLinks |= hassublinks;
  
! 	/* If we got this far, we must have added quals */
! 	return true;
  }
  
  /*
--- 268,276 ----
  	 * Mark this query as having row security, so plancache can invalidate
  	 * it when necessary (eg: role changes)
  	 */
! 	*hasRowSecurity = true;
  
! 	return;
  }
  
  /*
*************** pull_row_security_policies(CmdType cmd,
*** 383,389 ****
   * qual_eval, with_check_eval, and hassublinks are output variables
   */
  static void
! process_policies(List *policies, int rt_index, Expr **qual_eval,
  				 Expr **with_check_eval, bool *hassublinks)
  {
  	ListCell		   *item;
--- 376,382 ----
   * qual_eval, with_check_eval, and hassublinks are output variables
   */
  static void
! process_policies(Query* root, List *policies, int rt_index, Expr **qual_eval,
  				 Expr **with_check_eval, bool *hassublinks)
  {
  	ListCell		   *item;
*************** process_policies(List *policies, int rt_
*** 392,398 ****
  
  	/*
  	 * Extract the USING and WITH CHECK quals from each of the policies
! 	 * and add them to our lists.
  	 */
  	foreach(item, policies)
  	{
--- 385,392 ----
  
  	/*
  	 * Extract the USING and WITH CHECK quals from each of the policies
! 	 * and add them to our lists.  We only want WITH CHECK quals if this
! 	 * RTE is the query's result relation.
  	 */
  	foreach(item, policies)
  	{
*************** process_policies(List *policies, int rt_
*** 401,407 ****
  		if (policy->qual != NULL)
  			quals = lcons(copyObject(policy->qual), quals);
  
! 		if (policy->with_check_qual != NULL)
  			with_check_quals = lcons(copyObject(policy->with_check_qual),
  									 with_check_quals);
  
--- 395,402 ----
  		if (policy->qual != NULL)
  			quals = lcons(copyObject(policy->qual), quals);
  
! 		if (policy->with_check_qual != NULL &&
! 			rt_index == root->resultRelation)
  			with_check_quals = lcons(copyObject(policy->with_check_qual),
  									 with_check_quals);
  
*************** process_policies(List *policies, int rt_
*** 421,427 ****
  	 * If we end up with only USING quals, then use those as
  	 * WITH CHECK quals also.
  	 */
! 	if (with_check_quals == NIL)
  		with_check_quals = copyObject(quals);
  
  	/*
--- 416,422 ----
  	 * If we end up with only USING quals, then use those as
  	 * WITH CHECK quals also.
  	 */
! 	if (with_check_quals == NIL && rt_index == root->resultRelation)
  		with_check_quals = copyObject(quals);
  
  	/*
*************** process_policies(List *policies, int rt_
*** 450,457 ****
  	 */
  	if (list_length(with_check_quals) > 1)
  		*with_check_eval = makeBoolExpr(OR_EXPR, with_check_quals, -1);
! 	else
  		*with_check_eval = (Expr*) linitial(with_check_quals);
  
  	return;
  }
--- 445,454 ----
  	 */
  	if (list_length(with_check_quals) > 1)
  		*with_check_eval = makeBoolExpr(OR_EXPR, with_check_quals, -1);
! 	else if (with_check_quals != NIL)
  		*with_check_eval = (Expr*) linitial(with_check_quals);
+ 	else
+ 		*with_check_eval = NULL;
  
  	return;
  }
diff --git a/src/include/rewrite/rowsecurity.h b/src/include/rewrite/rowsecurity.h
new file mode 100644
index 8d19bfd..ac72427
*** a/src/include/rewrite/rowsecurity.h
--- b/src/include/rewrite/rowsecurity.h
*************** typedef List *(*row_security_policy_hook
*** 39,45 ****
  
  extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook;
  
! extern bool prepend_row_security_policies(Query* root, RangeTblEntry* rte,
! 									   int rt_index);
  
  #endif	/* ROWSECURITY_H */
--- 39,46 ----
  
  extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook;
  
! extern void get_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index,
! 						  List **securityQuals, List **withCheckOptions,
! 						  bool *hasRowSecurity, bool *hasSubLinks);
  
  #endif	/* ROWSECURITY_H */
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 44e8dab..5676079
*** a/src/test/regress/expected/rowsecurity.out
--- b/src/test/regress/expected/rowsecurity.out
*************** ALTER TABLE t1 DROP COLUMN junk1;    --
*** 466,474 ****
--- 466,476 ----
  GRANT ALL ON t1 TO public;
  COPY t1 FROM stdin WITH (oids);
  CREATE TABLE t2 (c float) INHERITS (t1);
+ GRANT ALL ON t2 TO public;
  COPY t2 FROM stdin WITH (oids);
  CREATE TABLE t3 (c text, b text, a int) WITH OIDS;
  ALTER TABLE t3 INHERIT t1;
+ GRANT ALL ON t3 TO public;
  COPY t3(a,b,c) FROM stdin WITH (oids);
  CREATE POLICY p1 ON t1 FOR ALL TO PUBLIC USING (a % 2 = 0); -- be even number
  CREATE POLICY p2 ON t2 FOR ALL TO PUBLIC USING (a % 2 = 1); -- be odd number
*************** NOTICE:  f_leak => yyyyyy
*** 1117,1138 ****
   302 | 2 | yyyyyy      | (2,yyyyyy)
  (5 rows)
  
  RESET SESSION AUTHORIZATION;
  SET row_security TO OFF;
! SELECT * FROM t1;
   a |      b      
  ---+-------------
   1 | aaa
-  3 | ccc
-  2 | bbbbbb_updt
-  4 | dddddd_updt
   1 | abc
-  3 | cde
-  2 | bcdbcd
-  4 | defdef
   1 | xxx
!  3 | zzz
   2 | yyyyyy
  (11 rows)
  
  SET SESSION AUTHORIZATION rls_regress_user1;
--- 1119,1334 ----
   302 | 2 | yyyyyy      | (2,yyyyyy)
  (5 rows)
  
+ -- updates with from clause
+ EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3
+ WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+                           QUERY PLAN                           
+ ---------------------------------------------------------------
+  Update on t2 t2_1
+    ->  Nested Loop
+          ->  Subquery Scan on t2
+                Filter: f_leak(t2.b)
+                ->  LockRows
+                      ->  Seq Scan on t2 t2_2
+                            Filter: ((a = 3) AND ((a % 2) = 1))
+          ->  Seq Scan on t3
+                Filter: (f_leak(b) AND (a = 2))
+ (9 rows)
+ 
+ UPDATE t2 SET b=t2.b FROM t3
+ WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+ NOTICE:  f_leak => cde
+ NOTICE:  f_leak => xxx
+ NOTICE:  f_leak => zzz
+ NOTICE:  f_leak => yyyyyy
+ EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
+ WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+                           QUERY PLAN                           
+ ---------------------------------------------------------------
+  Update on t1 t1_3
+    Update on t1 t1_3
+    Update on t2 t1
+    Update on t3 t1
+    ->  Nested Loop
+          ->  Subquery Scan on t1
+                Filter: f_leak(t1.b)
+                ->  LockRows
+                      ->  Seq Scan on t1 t1_4
+                            Filter: ((a = 3) AND ((a % 2) = 0))
+          ->  Subquery Scan on t2
+                Filter: f_leak(t2.b)
+                ->  Seq Scan on t2 t2_3
+                      Filter: ((a = 3) AND ((a % 2) = 1))
+    ->  Nested Loop
+          ->  Subquery Scan on t1_1
+                Filter: f_leak(t1_1.b)
+                ->  LockRows
+                      ->  Seq Scan on t2 t2_4
+                            Filter: ((a = 3) AND ((a % 2) = 0))
+          ->  Subquery Scan on t2_1
+                Filter: f_leak(t2_1.b)
+                ->  Seq Scan on t2 t2_5
+                      Filter: ((a = 3) AND ((a % 2) = 1))
+    ->  Nested Loop
+          ->  Subquery Scan on t1_2
+                Filter: f_leak(t1_2.b)
+                ->  LockRows
+                      ->  Seq Scan on t3
+                            Filter: ((a = 3) AND ((a % 2) = 0))
+          ->  Subquery Scan on t2_2
+                Filter: f_leak(t2_2.b)
+                ->  Seq Scan on t2 t2_6
+                      Filter: ((a = 3) AND ((a % 2) = 1))
+ (34 rows)
+ 
+ UPDATE t1 SET b=t1.b FROM t2
+ WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
+ WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+                              QUERY PLAN                              
+ ---------------------------------------------------------------------
+  Update on t2 t2_1
+    ->  Nested Loop
+          ->  Subquery Scan on t2
+                Filter: f_leak(t2.b)
+                ->  LockRows
+                      ->  Seq Scan on t2 t2_2
+                            Filter: ((a = 3) AND ((a % 2) = 1))
+          ->  Subquery Scan on t1
+                Filter: f_leak(t1.b)
+                ->  Result
+                      ->  Append
+                            ->  Seq Scan on t1 t1_1
+                                  Filter: ((a = 3) AND ((a % 2) = 0))
+                            ->  Seq Scan on t2 t2_3
+                                  Filter: ((a = 3) AND ((a % 2) = 0))
+                            ->  Seq Scan on t3
+                                  Filter: ((a = 3) AND ((a % 2) = 0))
+ (17 rows)
+ 
+ UPDATE t2 SET b=t2.b FROM t1
+ WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ NOTICE:  f_leak => cde
+ -- updates with from clause self join
+ EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+ WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+ AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+                           QUERY PLAN                           
+ ---------------------------------------------------------------
+  Update on t2 t2_1_1
+    ->  Nested Loop
+          Join Filter: (t2_1.b = t2_2.b)
+          ->  Subquery Scan on t2_1
+                Filter: f_leak(t2_1.b)
+                ->  LockRows
+                      ->  Seq Scan on t2 t2_1_2
+                            Filter: ((a = 3) AND ((a % 2) = 1))
+          ->  Subquery Scan on t2_2
+                Filter: f_leak(t2_2.b)
+                ->  Seq Scan on t2 t2_2_1
+                      Filter: ((a = 3) AND ((a % 2) = 1))
+ (12 rows)
+ 
+ UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+ WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+ AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+ NOTICE:  f_leak => cde
+ NOTICE:  f_leak => cde
+  a |  b  |  c  | a |  b  |  c  |    t2_1     |    t2_2     
+ ---+-----+-----+---+-----+-----+-------------+-------------
+  3 | cde | 3.3 | 3 | cde | 3.3 | (3,cde,3.3) | (3,cde,3.3)
+ (1 row)
+ 
+ EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+ WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+                           QUERY PLAN                           
+ ---------------------------------------------------------------
+  Update on t1 t1_1_3
+    Update on t1 t1_1_3
+    Update on t2 t1_1
+    Update on t3 t1_1
+    ->  Nested Loop
+          Join Filter: (t1_1.b = t1_2.b)
+          ->  Subquery Scan on t1_1
+                Filter: f_leak(t1_1.b)
+                ->  LockRows
+                      ->  Seq Scan on t1 t1_1_4
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+          ->  Subquery Scan on t1_2
+                Filter: f_leak(t1_2.b)
+                ->  Append
+                      ->  Seq Scan on t1 t1_2_3
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+                      ->  Seq Scan on t2 t1_2_4
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+                      ->  Seq Scan on t3 t1_2_5
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+    ->  Nested Loop
+          Join Filter: (t1_1_1.b = t1_2_1.b)
+          ->  Subquery Scan on t1_1_1
+                Filter: f_leak(t1_1_1.b)
+                ->  LockRows
+                      ->  Seq Scan on t2 t1_1_5
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+          ->  Subquery Scan on t1_2_1
+                Filter: f_leak(t1_2_1.b)
+                ->  Append
+                      ->  Seq Scan on t1 t1_2_6
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+                      ->  Seq Scan on t2 t1_2_7
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+                      ->  Seq Scan on t3 t1_2_8
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+    ->  Nested Loop
+          Join Filter: (t1_1_2.b = t1_2_2.b)
+          ->  Subquery Scan on t1_1_2
+                Filter: f_leak(t1_1_2.b)
+                ->  LockRows
+                      ->  Seq Scan on t3 t1_1_6
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+          ->  Subquery Scan on t1_2_2
+                Filter: f_leak(t1_2_2.b)
+                ->  Append
+                      ->  Seq Scan on t1 t1_2_9
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+                      ->  Seq Scan on t2 t1_2_10
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+                      ->  Seq Scan on t3 t1_2_11
+                            Filter: ((a = 4) AND ((a % 2) = 0))
+ (52 rows)
+ 
+ UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+ WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+ NOTICE:  f_leak => dddddd_updt
+ NOTICE:  f_leak => dddddd_updt
+ NOTICE:  f_leak => defdef
+ NOTICE:  f_leak => defdef
+ NOTICE:  f_leak => dddddd_updt
+ NOTICE:  f_leak => defdef
+  a |      b      | a |      b      |      t1_1       |      t1_2       
+ ---+-------------+---+-------------+-----------------+-----------------
+  4 | dddddd_updt | 4 | dddddd_updt | (4,dddddd_updt) | (4,dddddd_updt)
+  4 | defdef      | 4 | defdef      | (4,defdef)      | (4,defdef)
+ (2 rows)
+ 
  RESET SESSION AUTHORIZATION;
  SET row_security TO OFF;
! SELECT * FROM t1 ORDER BY a,b;
   a |      b      
  ---+-------------
   1 | aaa
   1 | abc
   1 | xxx
!  2 | bbbbbb_updt
!  2 | bcdbcd
   2 | yyyyyy
+  3 | ccc
+  3 | cde
+  3 | zzz
+  4 | dddddd_updt
+  4 | defdef
  (11 rows)
  
  SET SESSION AUTHORIZATION rls_regress_user1;
*************** NOTICE:  f_leak => yyyyyy
*** 1193,1198 ****
--- 1389,1491 ----
  (3 rows)
  
  --
+ -- S.b. view on top of Row-level security
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ CREATE TABLE b1 (a int, b text);
+ INSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+ CREATE POLICY p1 ON b1 USING (a % 2 = 0);
+ ALTER TABLE b1 ENABLE ROW LEVEL SECURITY;
+ GRANT ALL ON b1 TO rls_regress_user1;
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;
+ GRANT ALL ON bv1 TO rls_regress_user2;
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);
+                  QUERY PLAN                  
+ ---------------------------------------------
+  Subquery Scan on bv1
+    Filter: f_leak(bv1.b)
+    ->  Seq Scan on b1
+          Filter: ((a > 0) AND ((a % 2) = 0))
+ (4 rows)
+ 
+ SELECT * FROM bv1 WHERE f_leak(b);
+ NOTICE:  f_leak => c81e728d9d4c2f636f067f89cc14862c
+ NOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c
+ NOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+ NOTICE:  f_leak => c9f0f895fb98ab9159f51fd0297e236d
+ NOTICE:  f_leak => d3d9446802a44259755d38e6d163e820
+  a  |                b                 
+ ----+----------------------------------
+   2 | c81e728d9d4c2f636f067f89cc14862c
+   4 | a87ff679a2f3e71d9181a67b7542122c
+   6 | 1679091c5a880faf6fb5e6087eb1b2dc
+   8 | c9f0f895fb98ab9159f51fd0297e236d
+  10 | d3d9446802a44259755d38e6d163e820
+ (5 rows)
+ 
+ INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
+ ERROR:  new row violates WITH CHECK OPTION for "b1"
+ INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
+ ERROR:  new row violates WITH CHECK OPTION for "b1"
+ INSERT INTO bv1 VALUES (12, 'xxx'); -- ok
+ EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+                                 QUERY PLAN                                 
+ ---------------------------------------------------------------------------
+  Update on b1 b1_1
+    ->  Subquery Scan on b1
+          Filter: f_leak(b1.b)
+          ->  Subquery Scan on b1_2
+                ->  LockRows
+                      ->  Seq Scan on b1 b1_3
+                            Filter: ((a > 0) AND (a = 4) AND ((a % 2) = 0))
+ (7 rows)
+ 
+ UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+ NOTICE:  f_leak => a87ff679a2f3e71d9181a67b7542122c
+ EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+                                 QUERY PLAN                                 
+ ---------------------------------------------------------------------------
+  Delete on b1 b1_1
+    ->  Subquery Scan on b1
+          Filter: f_leak(b1.b)
+          ->  Subquery Scan on b1_2
+                ->  LockRows
+                      ->  Seq Scan on b1 b1_3
+                            Filter: ((a > 0) AND (a = 6) AND ((a % 2) = 0))
+ (7 rows)
+ 
+ DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+ NOTICE:  f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ SELECT * FROM b1;
+   a  |                b                 
+ -----+----------------------------------
+  -10 | 1b0fd9efa5279c4203b7c70233f86dbf
+   -9 | 252e691406782824eec43d7eadc3d256
+   -8 | a8d2ec85eaf98407310b72eb73dda247
+   -7 | 74687a12d3915d3c4d83f1af7b3683d5
+   -6 | 596a3d04481816330f07e4f97510c28f
+   -5 | 47c1b025fa18ea96c33fbb6718688c0f
+   -4 | 0267aaf632e87a63288a08331f22c7c3
+   -3 | b3149ecea4628efd23d2f86e5a723472
+   -2 | 5d7b9adcbe1c629ec722529dd12e5129
+   -1 | 6bb61e3b7bce0931da574d19d1d82c88
+    0 | cfcd208495d565ef66e7dff9f98764da
+    1 | c4ca4238a0b923820dcc509a6f75849b
+    2 | c81e728d9d4c2f636f067f89cc14862c
+    3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+    5 | e4da3b7fbbce2345d7772b0674a318d5
+    7 | 8f14e45fceea167a5a36dedd4bea2543
+    8 | c9f0f895fb98ab9159f51fd0297e236d
+    9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+   10 | d3d9446802a44259755d38e6d163e820
+   12 | xxx
+    4 | yyy
+ (21 rows)
+ 
+ --
  -- ROLE/GROUP
  --
  SET SESSION AUTHORIZATION rls_regress_user0;
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index ed7adbf..4da5035
*** a/src/test/regress/sql/rowsecurity.sql
--- b/src/test/regress/sql/rowsecurity.sql
*************** COPY t1 FROM stdin WITH (oids);
*** 207,212 ****
--- 207,214 ----
  \.
  
  CREATE TABLE t2 (c float) INHERITS (t1);
+ GRANT ALL ON t2 TO public;
+ 
  COPY t2 FROM stdin WITH (oids);
  201	1	abc	1.1
  202	2	bcd	2.2
*************** COPY t2 FROM stdin WITH (oids);
*** 216,221 ****
--- 218,225 ----
  
  CREATE TABLE t3 (c text, b text, a int) WITH OIDS;
  ALTER TABLE t3 INHERIT t1;
+ GRANT ALL ON t3 TO public;
+ 
  COPY t3(a,b,c) FROM stdin WITH (oids);
  301	1	xxx	X
  302	2	yyy	Y
*************** UPDATE only t1 SET b = b WHERE f_leak(b)
*** 423,431 ****
  UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;
  UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
  
  RESET SESSION AUTHORIZATION;
  SET row_security TO OFF;
! SELECT * FROM t1;
  
  SET SESSION AUTHORIZATION rls_regress_user1;
  SET row_security TO ON;
--- 427,471 ----
  UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;
  UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
  
+ -- updates with from clause
+ EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t3
+ WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+ 
+ UPDATE t2 SET b=t2.b FROM t3
+ WHERE t2.a = 3 and t3.a = 2 AND f_leak(t2.b) AND f_leak(t3.b);
+ 
+ EXPLAIN (COSTS OFF) UPDATE t1 SET b=t1.b FROM t2
+ WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ 
+ UPDATE t1 SET b=t1.b FROM t2
+ WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ 
+ EXPLAIN (COSTS OFF) UPDATE t2 SET b=t2.b FROM t1
+ WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ 
+ UPDATE t2 SET b=t2.b FROM t1
+ WHERE t1.a = 3 and t2.a = 3 AND f_leak(t1.b) AND f_leak(t2.b);
+ 
+ -- updates with from clause self join
+ EXPLAIN (COSTS OFF) UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+ WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+ AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+ 
+ UPDATE t2 t2_1 SET b = t2_2.b FROM t2 t2_2
+ WHERE t2_1.a = 3 AND t2_2.a = t2_1.a AND t2_2.b = t2_1.b
+ AND f_leak(t2_1.b) AND f_leak(t2_2.b) RETURNING *, t2_1, t2_2;
+ 
+ EXPLAIN (COSTS OFF) UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+ WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+ 
+ UPDATE t1 t1_1 SET b = t1_2.b FROM t1 t1_2
+ WHERE t1_1.a = 4 AND t1_2.a = t1_1.a AND t1_2.b = t1_1.b
+ AND f_leak(t1_1.b) AND f_leak(t1_2.b) RETURNING *, t1_1, t1_2;
+ 
  RESET SESSION AUTHORIZATION;
  SET row_security TO OFF;
! SELECT * FROM t1 ORDER BY a,b;
  
  SET SESSION AUTHORIZATION rls_regress_user1;
  SET row_security TO ON;
*************** DELETE FROM only t1 WHERE f_leak(b) RETU
*** 436,441 ****
--- 476,514 ----
  DELETE FROM t1 WHERE f_leak(b) RETURNING oid, *, t1;
  
  --
+ -- S.b. view on top of Row-level security
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ CREATE TABLE b1 (a int, b text);
+ INSERT INTO b1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+ 
+ CREATE POLICY p1 ON b1 USING (a % 2 = 0);
+ ALTER TABLE b1 ENABLE ROW LEVEL SECURITY;
+ GRANT ALL ON b1 TO rls_regress_user1;
+ 
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ CREATE VIEW bv1 WITH (security_barrier) AS SELECT * FROM b1 WHERE a > 0 WITH CHECK OPTION;
+ GRANT ALL ON bv1 TO rls_regress_user2;
+ 
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ 
+ EXPLAIN (COSTS OFF) SELECT * FROM bv1 WHERE f_leak(b);
+ SELECT * FROM bv1 WHERE f_leak(b);
+ 
+ INSERT INTO bv1 VALUES (-1, 'xxx'); -- should fail view WCO
+ INSERT INTO bv1 VALUES (11, 'xxx'); -- should fail RLS check
+ INSERT INTO bv1 VALUES (12, 'xxx'); -- ok
+ 
+ EXPLAIN (COSTS OFF) UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+ UPDATE bv1 SET b = 'yyy' WHERE a = 4 AND f_leak(b);
+ 
+ EXPLAIN (COSTS OFF) DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+ DELETE FROM bv1 WHERE a = 6 AND f_leak(b);
+ 
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ SELECT * FROM b1;
+ 
+ --
  -- ROLE/GROUP
  --
  SET SESSION AUTHORIZATION rls_regress_user0;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to