The attached patch is a revised one for DML permission checks.

List of updates:
- Source code comments in the patched functions were revised.
- ExecCheckRTPerms() and ExecCheckRTEPerms() were moved to aclchk.c,
  and renamed to chkpriv_relation_perms() and chkpriv_rte_perms().
- It took the 2nd argument (bool abort) that is a hint of behavior
  on access violations.
- It also returns AclResult, instead of bool.
- I assumed RI_Initial_Check() is not broken, right now.
  So, this patch just reworks DML permission checks without any bugfixes.
- The ESP hook were moved to ExecCheckRTPerms() from ExecCheckRTEPerms().
- At DoCopy() and RI_Initial_Check() call the checker function with
  list_make1(&rte), instead of &rte.
- In DoCopy(), required_access is used to store either ACL_SELECT or
  ACL_INSERT; initialized at head of the function.
- In DoCopy(), it initialize selectedCols or modifiedCol of RTE depending
  on if (is_from), instead of columnsSet.

ToDo:
- makeRangeTblEntry() stuff to allocate a RTE node with given parameter
  is not yet.

Thanks,

(2010/05/26 12:04), KaiGai Kohei wrote:
> (2010/05/26 11:12), Stephen Frost wrote:
>> * KaiGai Kohei (kai...@ak.jp.nec.com) wrote:
>>>> #2: REALLY BIG ISSUE- You've added ExecutorCheckPerms_hook as part of
>>>> this patch- don't, we're in feature-freeze right now and should not be
>>>> adding hooks at this time.
>>>
>>> The patch is intended to submit for the v9.1 development, not v9.0, isn't 
>>> it?
>>
>> That really depends on if this is actually fixing a bug in the existing
>> code or not.  I'm on the fence about that at the moment, to be honest.
>> I was trying to find if we expliitly say that SELECT rights are needed
>> to reference a column but wasn't able to.  If every code path is
>> expecting that, then perhaps we should just document it that way and
>> move on.  In that case, all these changes would be for 9.1.  If we
>> decide the current behavior is a bug, it might be something which could
>> be fixed in 9.0 and maybe back-patched.
> 
> Ahh, because I found out an independent problem during the discussion,
> it made us confused. Please make clear this patch does not intend to
> fix the bug.
> 
> If we decide it is an actual bug to be fixed/informed, I also agree
> it should be worked in a separated patch.
> 
> Well, rest of discussion should be haven in different thread.
> 
>> In *either* case, given that one is a 'clean-up' patch and the other is
>> 'new functionality', they should be independent *anyway*.  Small
>> incremental changes that don't break things when applied is what we're
>> shooting for here.
> 
> Agreed.
> 
>>>> #3: You didn't move ExecCheckRTPerms() and ExecCheckRTEPerms() to
>>>> utils/acl and instead added executor/executor.h to rt_triggers.c.
>>>> I don't particularly like that.  I admit that DoCopy() already knew
>>>> about the executor, and if that were the only case outside of the
>>>> executor where ExecCheckRTPerms() was getting called it'd probably be
>>>> alright, but we already have another place that wants to use it, so
>>>> let's move it to a more appropriate place.
>>>
>>> Sorry, I'm a bit confused.
>>> It seemed to me you suggested to utilize ExecCheckRTPerms() rather than
>>> moving its logic anywhere, so I kept it here. (Was it misunderstand?)
>>
>> I'm talking about moving the whole function (all 3 lines of it) to
>> somewhere else and then reworking the function to be more appropriate
>> based on it's new location (including renaming and changing arguments
>> and return values, as appropriate).
> 
> OK, I agreed.
> 
>>> If so, but, I doubt utils/acl is the best placeholder of the moved
>>> ExecCheckRTPerms(), because the checker function calls both of the
>>> default acl functions and a optional external security function.
>>
>> Can you explain why you think that having a function in utils/acl (eg:
>> include/utils/acl.h and backend/utils/aclchk.c) which calls default acl
>> functions and an allows for an external hook would be a bad idea?
>>
>>> It means the ExecCheckRTPerms() is caller of acl functions, not acl
>>> function itself, isn't it?
>>
>> It's providing a higher-level service, sure, but there's nothing
>> particularly interesting or special about what it's doing in this case,
>> and, we need it in multiple places.  Why duplicate it?
> 
> If number of the checker functions is only a reason why we move
> ExecCheckRTPerms() into the backend/utils/aclchk.c right now, I
> don't have any opposition.
> When it reaches to a dozen, we can consider new location. Right?
> 
> Sorry, the name of pg_rangetbl_aclcheck() was misleading for me.
> 
>>> I agreed the checker function is not a part of executor, but it is
>>> also not a part of acl functions in my opinion.
>>>
>>> If it is disinclined to create a new directory to deploy the checker
>>> function, my preference is src/backend/utils/adt/security.c and
>>> src/include/utils/security.h .
>>
>> We don't need a new directory or file for one function, as Robert
>> already pointed out.
> 
> OK, let's consider when aclchk.c holds a dozen of checker functions.
> 
>>>> #6: I havn't checked yet, but if there are other things in an RTE which
>>>> would make sense in the DoCopy case, beyond just what's needed for the
>>>> permissions checking, and which wouldn't be 'correct' with a NULL'd
>>>> value, I would set those.  Yes, we're building the RTE to check
>>>> permissions, but we don't want someone downstream to be suprised when
>>>> they make a change to something in the permissions checking and discover
>>>> that a value in RTE they expected to be there wasn't valid.  Even more
>>>> so, if there are function helpers which can be used to build an RTE, we
>>>> should be using them.  The same goes for RI_Initial_Check().
>>>
>>> Are you saying something like makeFuncExpr()?
>>> I basically agree. However, should it be done in this patch?
>>
>> Actually, I mean looking for, and using, things like
>> markRTEForSelectPriv() and addRangeTableEntry() or
>> addRangeTableEntryForRelation().
> 
> OK, I'll make it in separated patch.
> 
>>>> #8: When moving ExecCheckRTPerms(), you should rename it to be more like
>>>> the other function calls in acl.h  Perhaps pg_rangetbl_aclcheck()?
>>>> Also, it should return an actual AclResult instead of just true/false.
>>>
>>> See the comments in #3.
>>> And, if the caller has to handle aclcheck_error(), user cannot distinguish
>>> access violation errors between the default PG permission and any other
>>> external security stuff, isn't it?
>>
>> I'm not suggesting that the caller handle aclcheck_error()..
>> ExecCheckRTPerms() could just as easily have a flag which indicates if
>> it will call aclcheck_error() or not, and if not, to return an
>> AclResult to the caller.  That flag could then be passed to
>> ExecCheckRTEPerms() as you have it now.
> 
> Sorry, the name of pg_rangetbl_aclcheck() is also misleading for me.
> It makes me an impression that it always returns AclResult and caller handles
> it appropriately, like pg_class_aclcheck().
> 
> What you explained seems to me same as what I plan now.
> 
> Thanks,


-- 
KaiGai Kohei <kai...@ak.jp.nec.com>
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
***************
*** 4722,4724 **** get_user_default_acl(GrantObjectType objtype, Oid ownerId, Oid nsp_oid)
--- 4722,4937 ----
  
  	return result;
  }
+ 
+ /*
+  * External security provider hooks
+  */
+ chkpriv_relation_perms_hook_type chkpriv_relation_perms_hook = NULL;
+ 
+ /*
+  * chkpriv_rte_perms
+  *	A helper function of chkpriv_relation_perms.
+  *	It checks access permissions for a single RTE. If violated, it raises
+  *	an error or returned false depending on the 'abort' argument.
+  */
+ static AclResult
+ chkpriv_rte_perms(RangeTblEntry *rte, bool abort)
+ {
+ 	AclMode		requiredPerms;
+ 	AclMode		relPerms;
+ 	AclMode		remainingPerms;
+ 	Oid			relOid;
+ 	Oid			userid;
+ 	Bitmapset  *tmpset;
+ 	int			col;
+ 
+ 	/*
+ 	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
+ 	 * checked by init_fcache when the function is prepared for execution.
+ 	 * Join, subquery, and special RTEs need no checks.
+ 	 */
+ 	if (rte->rtekind != RTE_RELATION)
+ 		return ACLCHECK_OK;
+ 
+ 	/*
+ 	 * No work if requiredPerms is empty.
+ 	 */
+ 	requiredPerms = rte->requiredPerms;
+ 	if (requiredPerms == 0)
+ 		return ACLCHECK_OK;
+ 
+ 	relOid = rte->relid;
+ 
+ 	/*
+ 	 * userid to check as: current user unless we have a setuid indication.
+ 	 *
+ 	 * Note: GetUserId() is presently fast enough that there's no harm in
+ 	 * calling it separately for each RTE.	If that stops being true, we could
+ 	 * call it once in chkpriv_relation_perms and pass the userid down from
+ 	 * there. But for now, no need for the extra clutter.
+ 	 */
+ 	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+ 
+ 	/*
+ 	 * We must have *all* the requiredPerms bits, but some of the bits can be
+ 	 * satisfied from column-level rather than relation-level permissions.
+ 	 * First, remove any bits that are satisfied by relation permissions.
+ 	 */
+ 	relPerms = pg_class_aclmask(relOid, userid, requiredPerms, ACLMASK_ALL);
+ 	remainingPerms = requiredPerms & ~relPerms;
+ 	if (remainingPerms != 0)
+ 	{
+ 		/*
+ 		 * If we lack any permissions that exist only as relation permissions,
+ 		 * we can fail straight away.
+ 		 */
+ 		if (remainingPerms & ~(ACL_SELECT | ACL_INSERT | ACL_UPDATE))
+ 		{
+ 			if (abort)
+ 				aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 							   get_rel_name(relOid));
+ 			return ACLCHECK_NO_PRIV;
+ 		}
+ 
+ 		/*
+ 		 * Check to see if we have the needed privileges at column level.
+ 		 *
+ 		 * Note: failures just report a table-level error; it would be nicer
+ 		 * to report a column-level error if we have some but not all of the
+ 		 * column privileges.
+ 		 */
+ 		if (remainingPerms & ACL_SELECT)
+ 		{
+ 			/*
+ 			 * When the query doesn't explicitly reference any columns (for
+ 			 * example, SELECT COUNT(*) FROM table), allow the query if we
+ 			 * have SELECT on any column of the rel, as per SQL spec.
+ 			 */
+ 			if (bms_is_empty(rte->selectedCols))
+ 			{
+ 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
+ 											  ACLMASK_ANY) != ACLCHECK_OK)
+ 				{
+ 					if (abort)
+ 						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 									   get_rel_name(relOid));
+ 					return ACLCHECK_NO_PRIV;
+ 				}
+ 			}
+ 
+ 			tmpset = bms_copy(rte->selectedCols);
+ 			while ((col = bms_first_member(tmpset)) >= 0)
+ 			{
+ 				/* remove the column number offset */
+ 				col += FirstLowInvalidHeapAttributeNumber;
+ 				if (col == InvalidAttrNumber)
+ 				{
+ 					/* Whole-row reference, must have priv on all cols */
+ 					if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
+ 												  ACLMASK_ALL) != ACLCHECK_OK)
+ 					{
+ 						if (abort)
+ 							aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 										   get_rel_name(relOid));
+ 						return ACLCHECK_NO_PRIV;
+ 					}
+ 				}
+ 				else
+ 				{
+ 					if (pg_attribute_aclcheck(relOid, col, userid,
+ 											  ACL_SELECT) != ACLCHECK_OK)
+ 					{
+ 						if (abort)
+ 							aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 										   get_rel_name(relOid));
+ 						return ACLCHECK_NO_PRIV;
+ 					}
+ 				}
+ 			}
+ 			bms_free(tmpset);
+ 		}
+ 
+ 		/*
+ 		 * Basically the same for the mod columns, with either INSERT or
+ 		 * UPDATE privilege as specified by remainingPerms.
+ 		 */
+ 		remainingPerms &= ~ACL_SELECT;
+ 		if (remainingPerms != 0)
+ 		{
+ 			/*
+ 			 * When the query doesn't explicitly change any columns, allow the
+ 			 * query if we have permission on any column of the rel.  This is
+ 			 * to handle SELECT FOR UPDATE as well as possible corner cases in
+ 			 * INSERT and UPDATE.
+ 			 */
+ 			if (bms_is_empty(rte->modifiedCols))
+ 			{
+ 				if (pg_attribute_aclcheck_all(relOid, userid, remainingPerms,
+ 											  ACLMASK_ANY) != ACLCHECK_OK)
+ 				{
+ 					if (abort)
+ 						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 									   get_rel_name(relOid));
+ 					return ACLCHECK_NO_PRIV;
+ 				}
+ 			}
+ 
+ 			tmpset = bms_copy(rte->modifiedCols);
+ 			while ((col = bms_first_member(tmpset)) >= 0)
+ 			{
+ 				/* remove the column number offset */
+ 				col += FirstLowInvalidHeapAttributeNumber;
+ 				if (col == InvalidAttrNumber)
+ 				{
+ 					/* whole-row reference can't happen here */
+ 					elog(ERROR, "whole-row update is not implemented");
+ 				}
+ 				else
+ 				{
+ 					if (pg_attribute_aclcheck(relOid, col, userid,
+ 											  remainingPerms) != ACLCHECK_OK)
+ 					{
+ 						if (abort)
+ 							aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 										   get_rel_name(relOid));
+ 						return ACLCHECK_NO_PRIV;
+ 					}
+ 				}
+ 			}
+ 			bms_free(tmpset);
+ 		}
+ 	}
+ 	return ACLCHECK_OK;
+ }
+ 
+ /*
+  * chkpriv_relation_perms
+  *	It checks access permissions for all relations listed in a range table.
+  *	If violated, it raises an error or returns false depending on the 'abort'
+  *	argument.
+  *	It also invokes an external security provide to check the permissions.
+  *	If it is available, both of the default PG checks and external checks
+  *	have to allow the required accesses for the relations.
+  */
+ AclResult
+ chkpriv_relation_perms(List *rangeTable, bool abort)
+ {
+ 	ListCell	   *l;
+ 	RangeTblEntry  *rte;
+ 	AclResult		retval = ACLCHECK_OK;
+ 
+ 	foreach(l, rangeTable)
+ 	{
+ 		rte = (RangeTblEntry *) lfirst(l);
+ 
+ 		retval = chkpriv_rte_perms(rte, abort);
+ 		if (retval != ACLCHECK_OK)
+ 			return retval;
+ 	}
+ 
+ 	/* External security provider invocation */
+ 	if (chkpriv_relation_perms_hook)
+ 		retval = (*chkpriv_relation_perms_hook)(rangeTable, abort);
+ 
+ 	return retval;
+ }
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 21,26 ****
--- 21,27 ----
  #include <arpa/inet.h>
  
  #include "access/heapam.h"
+ #include "access/sysattr.h"
  #include "access/xact.h"
  #include "catalog/namespace.h"
  #include "catalog/pg_type.h"
***************
*** 726,733 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  	bool		force_quote_all = false;
  	bool		format_specified = false;
  	AclMode		required_access = (is_from ? ACL_INSERT : ACL_SELECT);
- 	AclMode		relPerms;
- 	AclMode		remainingPerms;
  	ListCell   *option;
  	TupleDesc	tupDesc;
  	int			num_phys_attrs;
--- 727,732 ----
***************
*** 988,993 **** DoCopy(const CopyStmt *stmt, const char *queryString)
--- 987,996 ----
  
  	if (stmt->relation)
  	{
+ 		RangeTblEntry	rte;
+ 		List		   *attnums;
+ 		ListCell	   *cur;
+ 
  		Assert(!stmt->query);
  		cstate->queryDesc = NULL;
  
***************
*** 998,1025 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  		tupDesc = RelationGetDescr(cstate->rel);
  
  		/* Check relation permissions. */
! 		relPerms = pg_class_aclmask(RelationGetRelid(cstate->rel), GetUserId(),
! 									required_access, ACLMASK_ALL);
! 		remainingPerms = required_access & ~relPerms;
! 		if (remainingPerms != 0)
  		{
! 			/* We don't have table permissions, check per-column permissions */
! 			List	   *attnums;
! 			ListCell   *cur;
  
! 			attnums = CopyGetAttnums(tupDesc, cstate->rel, attnamelist);
! 			foreach(cur, attnums)
! 			{
! 				int			attnum = lfirst_int(cur);
! 
! 				if (pg_attribute_aclcheck(RelationGetRelid(cstate->rel),
! 										  attnum,
! 										  GetUserId(),
! 										  remainingPerms) != ACLCHECK_OK)
! 					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
! 								   RelationGetRelationName(cstate->rel));
! 			}
  		}
  
  		/* check read-only transaction */
  		if (XactReadOnly && is_from && !cstate->rel->rd_islocaltemp)
--- 1001,1023 ----
  		tupDesc = RelationGetDescr(cstate->rel);
  
  		/* Check relation permissions. */
! 		memset(&rte, 0, sizeof(rte));
! 		rte.type = T_RangeTblEntry;
! 		rte.rtekind = RTE_RELATION;
! 		rte.relid = RelationGetRelid(cstate->rel);
! 		rte.requiredPerms = required_access;
! 
! 		attnums = CopyGetAttnums(tupDesc, cstate->rel, attnamelist);
! 		foreach (cur, attnums)
  		{
! 			int	attnum = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;
  
! 			if (is_from)
! 				rte.modifiedCols = bms_add_member(rte.modifiedCols, attnum);
! 			else
! 				rte.selectedCols = bms_add_member(rte.selectedCols, attnum);
  		}
+ 		chkpriv_relation_perms(list_make1(&rte), true);
  
  		/* check read-only transaction */
  		if (XactReadOnly && is_from && !cstate->rel->rd_islocaltemp)
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 72,79 **** static void ExecutePlan(EState *estate, PlanState *planstate,
  			long numberTuples,
  			ScanDirection direction,
  			DestReceiver *dest);
- static void ExecCheckRTPerms(List *rangeTable);
- static void ExecCheckRTEPerms(RangeTblEntry *rte);
  static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
  static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
  				  Plan *planTree);
--- 72,77 ----
***************
*** 402,572 **** ExecutorRewind(QueryDesc *queryDesc)
  	MemoryContextSwitchTo(oldcontext);
  }
  
- 
- /*
-  * ExecCheckRTPerms
-  *		Check access permissions for all relations listed in a range table.
-  */
- static void
- ExecCheckRTPerms(List *rangeTable)
- {
- 	ListCell   *l;
- 
- 	foreach(l, rangeTable)
- 	{
- 		ExecCheckRTEPerms((RangeTblEntry *) lfirst(l));
- 	}
- }
- 
- /*
-  * ExecCheckRTEPerms
-  *		Check access permissions for a single RTE.
-  */
- static void
- ExecCheckRTEPerms(RangeTblEntry *rte)
- {
- 	AclMode		requiredPerms;
- 	AclMode		relPerms;
- 	AclMode		remainingPerms;
- 	Oid			relOid;
- 	Oid			userid;
- 	Bitmapset  *tmpset;
- 	int			col;
- 
- 	/*
- 	 * Only plain-relation RTEs need to be checked here.  Function RTEs are
- 	 * checked by init_fcache when the function is prepared for execution.
- 	 * Join, subquery, and special RTEs need no checks.
- 	 */
- 	if (rte->rtekind != RTE_RELATION)
- 		return;
- 
- 	/*
- 	 * No work if requiredPerms is empty.
- 	 */
- 	requiredPerms = rte->requiredPerms;
- 	if (requiredPerms == 0)
- 		return;
- 
- 	relOid = rte->relid;
- 
- 	/*
- 	 * userid to check as: current user unless we have a setuid indication.
- 	 *
- 	 * Note: GetUserId() is presently fast enough that there's no harm in
- 	 * calling it separately for each RTE.	If that stops being true, we could
- 	 * call it once in ExecCheckRTPerms and pass the userid down from there.
- 	 * But for now, no need for the extra clutter.
- 	 */
- 	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
- 
- 	/*
- 	 * We must have *all* the requiredPerms bits, but some of the bits can be
- 	 * satisfied from column-level rather than relation-level permissions.
- 	 * First, remove any bits that are satisfied by relation permissions.
- 	 */
- 	relPerms = pg_class_aclmask(relOid, userid, requiredPerms, ACLMASK_ALL);
- 	remainingPerms = requiredPerms & ~relPerms;
- 	if (remainingPerms != 0)
- 	{
- 		/*
- 		 * If we lack any permissions that exist only as relation permissions,
- 		 * we can fail straight away.
- 		 */
- 		if (remainingPerms & ~(ACL_SELECT | ACL_INSERT | ACL_UPDATE))
- 			aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
- 						   get_rel_name(relOid));
- 
- 		/*
- 		 * Check to see if we have the needed privileges at column level.
- 		 *
- 		 * Note: failures just report a table-level error; it would be nicer
- 		 * to report a column-level error if we have some but not all of the
- 		 * column privileges.
- 		 */
- 		if (remainingPerms & ACL_SELECT)
- 		{
- 			/*
- 			 * When the query doesn't explicitly reference any columns (for
- 			 * example, SELECT COUNT(*) FROM table), allow the query if we
- 			 * have SELECT on any column of the rel, as per SQL spec.
- 			 */
- 			if (bms_is_empty(rte->selectedCols))
- 			{
- 				if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
- 											  ACLMASK_ANY) != ACLCHECK_OK)
- 					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
- 								   get_rel_name(relOid));
- 			}
- 
- 			tmpset = bms_copy(rte->selectedCols);
- 			while ((col = bms_first_member(tmpset)) >= 0)
- 			{
- 				/* remove the column number offset */
- 				col += FirstLowInvalidHeapAttributeNumber;
- 				if (col == InvalidAttrNumber)
- 				{
- 					/* Whole-row reference, must have priv on all cols */
- 					if (pg_attribute_aclcheck_all(relOid, userid, ACL_SELECT,
- 												  ACLMASK_ALL) != ACLCHECK_OK)
- 						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
- 									   get_rel_name(relOid));
- 				}
- 				else
- 				{
- 					if (pg_attribute_aclcheck(relOid, col, userid, ACL_SELECT)
- 						!= ACLCHECK_OK)
- 						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
- 									   get_rel_name(relOid));
- 				}
- 			}
- 			bms_free(tmpset);
- 		}
- 
- 		/*
- 		 * Basically the same for the mod columns, with either INSERT or
- 		 * UPDATE privilege as specified by remainingPerms.
- 		 */
- 		remainingPerms &= ~ACL_SELECT;
- 		if (remainingPerms != 0)
- 		{
- 			/*
- 			 * When the query doesn't explicitly change any columns, allow the
- 			 * query if we have permission on any column of the rel.  This is
- 			 * to handle SELECT FOR UPDATE as well as possible corner cases in
- 			 * INSERT and UPDATE.
- 			 */
- 			if (bms_is_empty(rte->modifiedCols))
- 			{
- 				if (pg_attribute_aclcheck_all(relOid, userid, remainingPerms,
- 											  ACLMASK_ANY) != ACLCHECK_OK)
- 					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
- 								   get_rel_name(relOid));
- 			}
- 
- 			tmpset = bms_copy(rte->modifiedCols);
- 			while ((col = bms_first_member(tmpset)) >= 0)
- 			{
- 				/* remove the column number offset */
- 				col += FirstLowInvalidHeapAttributeNumber;
- 				if (col == InvalidAttrNumber)
- 				{
- 					/* whole-row reference can't happen here */
- 					elog(ERROR, "whole-row update is not implemented");
- 				}
- 				else
- 				{
- 					if (pg_attribute_aclcheck(relOid, col, userid, remainingPerms)
- 						!= ACLCHECK_OK)
- 						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
- 									   get_rel_name(relOid));
- 				}
- 			}
- 			bms_free(tmpset);
- 		}
- 	}
- }
- 
  /*
   * Check that the query does not imply any writes to non-temp tables.
   *
--- 400,405 ----
***************
*** 630,636 **** InitPlan(QueryDesc *queryDesc, int eflags)
  	/*
  	 * Do permissions checks
  	 */
! 	ExecCheckRTPerms(rangeTable);
  
  	/*
  	 * initialize the node's execution state
--- 463,469 ----
  	/*
  	 * Do permissions checks
  	 */
! 	chkpriv_relation_perms(rangeTable, true);
  
  	/*
  	 * initialize the node's execution state
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 30,35 ****
--- 30,36 ----
  
  #include "postgres.h"
  
+ #include "access/sysattr.h"
  #include "access/xact.h"
  #include "catalog/pg_constraint.h"
  #include "catalog/pg_operator.h"
***************
*** 2624,2629 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
--- 2625,2633 ----
  	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
  	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
  	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+ 	Bitmapset  *pkColumns = NULL;
+ 	Bitmapset  *fkColumns = NULL;
+ 	RangeTblEntry	rte;
  	const char *sep;
  	int			i;
  	int			old_work_mem;
***************
*** 2632,2649 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
  	SPIPlanPtr	qplan;
  
  	/*
  	 * Check to make sure current user has enough permissions to do the test
  	 * query.  (If not, caller can fall back to the trigger method, which
  	 * works because it changes user IDs on the fly.)
  	 *
  	 * XXX are there any other show-stopper conditions to check?
  	 */
! 	if (pg_class_aclcheck(RelationGetRelid(fk_rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
! 		return false;
! 	if (pg_class_aclcheck(RelationGetRelid(pk_rel), GetUserId(), ACL_SELECT) != ACLCHECK_OK)
  		return false;
  
! 	ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
  
  	/*----------
  	 * The query string built is:
--- 2636,2674 ----
  	SPIPlanPtr	qplan;
  
  	/*
+ 	 * Set up RI_ConstraintInfo
+ 	 */
+ 	ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
+ 
+ 	/*
  	 * Check to make sure current user has enough permissions to do the test
  	 * query.  (If not, caller can fall back to the trigger method, which
  	 * works because it changes user IDs on the fly.)
  	 *
  	 * XXX are there any other show-stopper conditions to check?
  	 */
! 	for (i = 0; i < riinfo.nkeys; i++)
! 	{
! 		fkColumns = bms_add_member(fkColumns, riinfo.fk_attnums[i]
! 								   - FirstLowInvalidHeapAttributeNumber);
! 		pkColumns = bms_add_member(pkColumns, riinfo.pk_attnums[i]
! 								   - FirstLowInvalidHeapAttributeNumber);
! 	}
! 
! 	memset(&rte, 0, sizeof(rte));
! 	rte.type = T_RangeTblEntry;
! 	rte.rtekind = RTE_RELATION;
! 	rte.requiredPerms = ACL_SELECT;
! 
! 	rte.relid = RelationGetRelid(fk_rel);
! 	rte.selectedCols = fkColumns;
! 	if (chkpriv_relation_perms(list_make1(&rte), false) != ACLCHECK_OK)
  		return false;
  
! 	rte.relid = RelationGetRelid(pk_rel);
! 	rte.selectedCols = pkColumns;
! 	if (chkpriv_relation_perms(list_make1(&rte), false) != ACLCHECK_OK)
! 		return false;
  
  	/*----------
  	 * The query string built is:
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
***************
*** 311,314 **** extern bool pg_ts_dict_ownercheck(Oid dict_oid, Oid roleid);
--- 311,321 ----
  extern bool pg_ts_config_ownercheck(Oid cfg_oid, Oid roleid);
  extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
  
+ /*
+  * security checker functions and hooks
+  */
+ typedef AclResult (*chkpriv_relation_perms_hook_type)(List *, bool);
+ extern PGDLLIMPORT chkpriv_relation_perms_hook_type chkpriv_relation_perms_hook;
+ extern AclResult chkpriv_relation_perms(List *rangeTable, bool abort);
+ 
  #endif   /* ACL_H */
-- 
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