Stephen, thanks for comments.

The attached three patches are the revised and divided ones.

 A: add makeRangeTblEntry()
 B: major reworks of DML permission checks
 C: add an ESP hook on the DML permission checks

(2010/05/27 0:09), Stephen Frost wrote:
> KaiGai,
> 
> * KaiGai Kohei (kai...@ak.jp.nec.com) wrote:
>> The attached patch is a revised one for DML permission checks.
> 
> This is certainly alot better.
> 
>> ToDo:
>> - makeRangeTblEntry() stuff to allocate a RTE node with given parameter
>>    is not yet.
> 
> I'd certainly like to see the above done, or to understand why it can't
> be if that turns out to be the case.

The patch-A tries to implement makeRangeTblEntry() which takes only rtekind
as argument right now.
Other fields are initialized to zero, using makeNode().

> A couple of other comments, all pretty minor things:
> 
> - I'd still rather see the hook itself in another patch, but given that
>    we've determined that none of this is going to go into 9.0, it's not
>    as big a deal.

OK, I divided the ESP hook part into the patch-C.

> - The hook definition in aclchk.c should really be at the top of that
>    file.  We've been pretty consistant about putting hooks at the top of
>    files instead of deep down in the file, this should also follow that
>    scheme.

OK, I moved it.

> - Some of the comments at the top of chkpriv_rte_perms probably make
>    sense to move up to where it's called from execMain.c.  Specifically,
>    the comments about the other RTE types (function, join, subquery).
>    I'd probably change the comment in chkpriv_rte_perms to be simpler-
>    "This is only used for checking plain relation permissions, nothing
>    else is checked here", and also have that same comment around
>    chkpriv_relation_perms, both in aclchk.c and in acl.h.

OK, I edited the comment as follows:

|   /*
|    * Do permissions checks. The check_relation_privileges() checks access
|    * permissions for all relations listed in a range table, but does not
|    * check anything for other RTE types (function, join, subquery, ...).
|    * Function RTEs are checked by init_fcache when the function is prepared
|    * for execution. Join, subquery, and special RTEs need no checks.
|    */

> - I'd move chkpriv_relation_perms above chkpriv_rte_perms, it's what we
>    expect people to use, after all.

OK, I reordered it.

> - Don't particularly like the function names.  How about
>    relation_privilege_check?  Or rangetbl_privilege_check?  We don't use
>    'perms' much (uh, at all?) in function names, and even if we did, it'd
>    be redundant and not really help someone understand what the function
>    is doing.

IIRC, Robert suggested that a verb should lead the function name.
So, I renamed it into check_relation_privileges() and check_rte_privileges().

> - I don't really like having 'abort' as the variable name for the 2nd
>    argument.  I'm not finding an obvious convention right now, but maybe
>    something like "error_on_failure" instead?

The 'failure' may make an impression of generic errors not only permission 
denied.
How about 'error_on_violation'?

> - In DoCopy, some comments about what you're doing there to set up for
>    calling chkpriv_relation_perms would be good (like the comment you
>    removed- /* We don't have table permissions, check per-column
>    permissions */, updated to for something like "build an RTE with the
>    columns referenced marked to check for necessary privileges").
>    Additionally, it might be worth considering if having an RTE built
>    farther up in DoCopy would make sense and would then be usable for
>    other bits in DoCopy.

I edited the comments as follows:

| /*
|  * Check relation permissions.
|  * We built an RTE with the relation and columns to be accessed
|  * to check for necessary privileges in the common way.
|  */

> - In RI_Initial_Check, why not build up an actual list of RTEs and just
>    call chkpriv_relation_perms once?  Also, you should add comments
>    there, again, about what you're doing and why.  If you can use another
>    function to build the actual RTE, this will probably fall out more
>    sensibly too.

Good catch! I fixed the invocation of checker function with list_make2().

And, I edited the comments as follows:

| /*
|  * We built a pair of RTEs of FK/PK relations and columns referenced
|  * in the test query to check necessary permission in the common way.
|  */

> - Have you checked if there are any bad side-effects from calling
>    ri_FetchConstraintInfo before doing the permissions checking?

The ri_FetchConstraintInfo() only references SysCaches to set up given
local variable without any other locks except for ones acquired by syscache.c.

> - The hook in acl.h should be separated out and brought to the top and
>    documented independently as to exactly where the hook is and what it
>    can be used for, along with what the arguments mean, etc.  Similairly,
>    chkpriv_relation_perms should really have a short comment for it about
>    what it's for.  Something more than 'security checker function'.

OK, at the patch-C, I moved the definition of the hook into the first half
of acl.h, but it needs to be declared after the AclResult definition.

BTW, I wonder whether acl.h is a correct place to explain about the hook,
although I added comments for the hook.
I think we should add a series of explanation about ESP hooks in the internal
section of the documentation, when the number of hooks reaches a dozen for
example.

Thanks,
-- 
KaiGai Kohei <kai...@ak.jp.nec.com>
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
***************
*** 135,140 **** static AclMode pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnu
--- 135,144 ----
  
  static AclResult check_rte_privileges(RangeTblEntry *rte, bool error_on_violation);
  
+ /*
+  * External security provider hooks
+  */
+ check_relation_privileges_hook_type check_relation_privileges_hook = NULL;
  
  #ifdef ACLDEBUG
  static void
***************
*** 4730,4735 **** get_user_default_acl(GrantObjectType objtype, Oid ownerId, Oid nsp_oid)
--- 4734,4742 ----
   *	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
  check_relation_privileges(List *rangeTable, bool error_on_violation)
***************
*** 4746,4751 **** check_relation_privileges(List *rangeTable, bool error_on_violation)
--- 4753,4763 ----
  		if (retval != ACLCHECK_OK)
  			return retval;
  	}
+ 
+ 	/* External security provider invocation */
+ 	if (check_relation_privileges_hook)
+ 		retval = (*check_relation_privileges_hook)(rangeTable,
+ 												   error_on_violation);
  	return retval;
  }
  
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 466,471 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 466,475 ----
  	 * check anything for other RTE types (function, join, subquery, ...).
  	 * Function RTEs are checked by init_fcache when the function is prepared
  	 * for execution. Join, subquery, and special RTEs need no checks.
+ 	 *
+ 	 * If available, it also invokes an external security provider. In this
+ 	 * case, both of the default PG checks and the external checks have to
+ 	 * allow the required accesses on the relation
  	 */
  	check_relation_privileges(rangeTable, true);
  
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
***************
*** 196,201 **** typedef enum AclObjectKind
--- 196,216 ----
  	MAX_ACL_KIND				/* MUST BE LAST */
  } AclObjectKind;
  
+ /*
+  * External security provider hooks
+  */
+ 
+ /*
+  * check_relation_privileges_hook
+  *	It allows an ESP to get control at check_relation_privileges().
+  *	A list of RangeTblEntry to be referenced and a flag to inform preferable
+  *	bahavior on access violations.
+  *	The ESP shall return ACLCHECK_OK if it allows the required access.
+  *	Elsewhere, it raises an error or return other AclResult status depending
+  *	on the given flag.
+  */
+ typedef AclResult (*check_relation_privileges_hook_type)(List *, bool);
+ extern PGDLLIMPORT check_relation_privileges_hook_type check_relation_privileges_hook;
  
  /*
   * routines used internally
*** a/src/backend/nodes/makefuncs.c
--- b/src/backend/nodes/makefuncs.c
***************
*** 262,267 **** makeRelabelType(Expr *arg, Oid rtype, int32 rtypmod, CoercionForm rformat)
--- 262,281 ----
  }
  
  /*
+  * makeRangeTblEntry
+  *	  creates a RangeTblEntry node
+  */
+ RangeTblEntry *
+ makeRangeTblEntry(RTEKind rtekind)
+ {
+ 	RangeTblEntry  *r = makeNode(RangeTblEntry);
+ 
+ 	r->rtekind = rtekind;
+ 
+ 	return r;
+ }
+ 
+ /*
   * makeRangeVar -
   *	  creates a RangeVar node (rather oversimplified case)
   */
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
***************
*** 877,888 **** addRangeTableEntry(ParseState *pstate,
  				   bool inh,
  				   bool inFromCl)
  {
! 	RangeTblEntry *rte = makeNode(RangeTblEntry);
  	char	   *refname = alias ? alias->aliasname : relation->relname;
  	LOCKMODE	lockmode;
  	Relation	rel;
  
- 	rte->rtekind = RTE_RELATION;
  	rte->alias = alias;
  
  	/*
--- 877,887 ----
  				   bool inh,
  				   bool inFromCl)
  {
! 	RangeTblEntry *rte = makeRangeTblEntry(RTE_RELATION);
  	char	   *refname = alias ? alias->aliasname : relation->relname;
  	LOCKMODE	lockmode;
  	Relation	rel;
  
  	rte->alias = alias;
  
  	/*
***************
*** 950,959 **** addRangeTableEntryForRelation(ParseState *pstate,
  							  bool inh,
  							  bool inFromCl)
  {
! 	RangeTblEntry *rte = makeNode(RangeTblEntry);
  	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
  
- 	rte->rtekind = RTE_RELATION;
  	rte->alias = alias;
  	rte->relid = RelationGetRelid(rel);
  
--- 949,957 ----
  							  bool inh,
  							  bool inFromCl)
  {
! 	RangeTblEntry *rte = makeRangeTblEntry(RTE_RELATION);
  	char	   *refname = alias ? alias->aliasname : RelationGetRelationName(rel);
  
  	rte->alias = alias;
  	rte->relid = RelationGetRelid(rel);
  
***************
*** 1004,1017 **** addRangeTableEntryForSubquery(ParseState *pstate,
  							  Alias *alias,
  							  bool inFromCl)
  {
! 	RangeTblEntry *rte = makeNode(RangeTblEntry);
  	char	   *refname = alias->aliasname;
  	Alias	   *eref;
  	int			numaliases;
  	int			varattno;
  	ListCell   *tlistitem;
  
- 	rte->rtekind = RTE_SUBQUERY;
  	rte->relid = InvalidOid;
  	rte->subquery = subquery;
  	rte->alias = alias;
--- 1002,1014 ----
  							  Alias *alias,
  							  bool inFromCl)
  {
! 	RangeTblEntry *rte = makeRangeTblEntry(RTE_SUBQUERY);
  	char	   *refname = alias->aliasname;
  	Alias	   *eref;
  	int			numaliases;
  	int			varattno;
  	ListCell   *tlistitem;
  
  	rte->relid = InvalidOid;
  	rte->subquery = subquery;
  	rte->alias = alias;
***************
*** 1084,1090 **** addRangeTableEntryForFunction(ParseState *pstate,
  							  RangeFunction *rangefunc,
  							  bool inFromCl)
  {
! 	RangeTblEntry *rte = makeNode(RangeTblEntry);
  	TypeFuncClass functypclass;
  	Oid			funcrettype;
  	TupleDesc	tupdesc;
--- 1081,1087 ----
  							  RangeFunction *rangefunc,
  							  bool inFromCl)
  {
! 	RangeTblEntry *rte = makeRangeTblEntry(RTE_FUNCTION);
  	TypeFuncClass functypclass;
  	Oid			funcrettype;
  	TupleDesc	tupdesc;
***************
*** 1092,1098 **** addRangeTableEntryForFunction(ParseState *pstate,
  	List	   *coldeflist = rangefunc->coldeflist;
  	Alias	   *eref;
  
- 	rte->rtekind = RTE_FUNCTION;
  	rte->relid = InvalidOid;
  	rte->subquery = NULL;
  	rte->funcexpr = funcexpr;
--- 1089,1094 ----
***************
*** 1217,1229 **** addRangeTableEntryForValues(ParseState *pstate,
  							Alias *alias,
  							bool inFromCl)
  {
! 	RangeTblEntry *rte = makeNode(RangeTblEntry);
  	char	   *refname = alias ? alias->aliasname : pstrdup("*VALUES*");
  	Alias	   *eref;
  	int			numaliases;
  	int			numcolumns;
  
- 	rte->rtekind = RTE_VALUES;
  	rte->relid = InvalidOid;
  	rte->subquery = NULL;
  	rte->values_lists = exprs;
--- 1213,1224 ----
  							Alias *alias,
  							bool inFromCl)
  {
! 	RangeTblEntry *rte = makeRangeTblEntry(RTE_VALUES);
  	char	   *refname = alias ? alias->aliasname : pstrdup("*VALUES*");
  	Alias	   *eref;
  	int			numaliases;
  	int			numcolumns;
  
  	rte->relid = InvalidOid;
  	rte->subquery = NULL;
  	rte->values_lists = exprs;
***************
*** 1291,1297 **** addRangeTableEntryForJoin(ParseState *pstate,
  						  Alias *alias,
  						  bool inFromCl)
  {
! 	RangeTblEntry *rte = makeNode(RangeTblEntry);
  	Alias	   *eref;
  	int			numaliases;
  
--- 1286,1292 ----
  						  Alias *alias,
  						  bool inFromCl)
  {
! 	RangeTblEntry *rte = makeRangeTblEntry(RTE_JOIN);
  	Alias	   *eref;
  	int			numaliases;
  
***************
*** 1305,1311 **** addRangeTableEntryForJoin(ParseState *pstate,
  				 errmsg("joins can have at most %d columns",
  						MaxAttrNumber)));
  
- 	rte->rtekind = RTE_JOIN;
  	rte->relid = InvalidOid;
  	rte->subquery = NULL;
  	rte->jointype = jointype;
--- 1300,1305 ----
***************
*** 1361,1374 **** addRangeTableEntryForCTE(ParseState *pstate,
  						 Alias *alias,
  						 bool inFromCl)
  {
! 	RangeTblEntry *rte = makeNode(RangeTblEntry);
  	char	   *refname = alias ? alias->aliasname : cte->ctename;
  	Alias	   *eref;
  	int			numaliases;
  	int			varattno;
  	ListCell   *lc;
  
- 	rte->rtekind = RTE_CTE;
  	rte->ctename = cte->ctename;
  	rte->ctelevelsup = levelsup;
  
--- 1355,1367 ----
  						 Alias *alias,
  						 bool inFromCl)
  {
! 	RangeTblEntry *rte = makeRangeTblEntry(RTE_CTE);
  	char	   *refname = alias ? alias->aliasname : cte->ctename;
  	Alias	   *eref;
  	int			numaliases;
  	int			varattno;
  	ListCell   *lc;
  
  	rte->ctename = cte->ctename;
  	rte->ctelevelsup = levelsup;
  
*** a/src/include/nodes/makefuncs.h
--- b/src/include/nodes/makefuncs.h
***************
*** 56,61 **** extern Alias *makeAlias(const char *aliasname, List *colnames);
--- 56,62 ----
  extern RelabelType *makeRelabelType(Expr *arg, Oid rtype, int32 rtypmod,
  				CoercionForm rformat);
  
+ extern RangeTblEntry *makeRangeTblEntry(RTEKind rtekind);
  extern RangeVar *makeRangeVar(char *schemaname, char *relname, int location);
  
  extern TypeName *makeTypeName(char *typnam);
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
***************
*** 133,138 **** static AclMode restrict_and_check_grant(bool is_grant, AclMode avail_goptions,
--- 133,140 ----
  static AclMode pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum,
  		   Oid roleid, AclMode mask, AclMaskHow how);
  
+ static AclResult check_rte_privileges(RangeTblEntry *rte, bool error_on_violation);
+ 
  
  #ifdef ACLDEBUG
  static void
***************
*** 4722,4724 **** get_user_default_acl(GrantObjectType objtype, Oid ownerId, Oid nsp_oid)
--- 4724,4923 ----
  
  	return result;
  }
+ 
+ /*
+  * check_relation_privileges
+  *	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.
+  */
+ AclResult
+ check_relation_privileges(List *rangeTable, bool error_on_violation)
+ {
+ 	ListCell	   *l;
+ 	RangeTblEntry  *rte;
+ 	AclResult		retval = ACLCHECK_OK;
+ 
+ 	foreach(l, rangeTable)
+ 	{
+ 		rte = (RangeTblEntry *) lfirst(l);
+ 
+ 		retval = check_rte_privileges(rte, error_on_violation);
+ 		if (retval != ACLCHECK_OK)
+ 			return retval;
+ 	}
+ 	return retval;
+ }
+ 
+ /*
+  * check_rte_privileges
+  *	This is only used for checking plain relation permissions, by
+  *	check_relation_privileges(), nothing else shall be checked here.
+  */
+ static AclResult
+ check_rte_privileges(RangeTblEntry *rte, bool error_on_violation)
+ {
+ 	AclMode		requiredPerms;
+ 	AclMode		relPerms;
+ 	AclMode		remainingPerms;
+ 	Oid			relOid;
+ 	Oid			userid;
+ 	Bitmapset  *tmpset;
+ 	int			col;
+ 
+ 	/*
+ 	 * Only plain-relation RTEs need to be checked here.
+ 	 */
+ 	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 (error_on_violation)
+ 				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 (error_on_violation)
+ 						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 (error_on_violation)
+ 							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 (error_on_violation)
+ 							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 (error_on_violation)
+ 						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 (error_on_violation)
+ 							aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 										   get_rel_name(relOid));
+ 						return ACLCHECK_NO_PRIV;
+ 					}
+ 				}
+ 			}
+ 			bms_free(tmpset);
+ 		}
+ 	}
+ 	return ACLCHECK_OK;
+ }
*** 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;
  
***************
*** 997,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)
--- 1000,1025 ----
  
  		tupDesc = RelationGetDescr(cstate->rel);
  
! 		/*
! 		 * Check relation permissions.
! 		 * We built an RTE with the relation and columns to be accessed
! 		 * to check for necessary privileges in the common way.
! 		 */
! 		rte = makeRangeTblEntry(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);
  		}
+ 		check_relation_privileges(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 ----
***************
*** 628,636 **** InitPlan(QueryDesc *queryDesc, int eflags)
  	int			i;
  
  	/*
! 	 * Do permissions checks
  	 */
! 	ExecCheckRTPerms(rangeTable);
  
  	/*
  	 * initialize the node's execution state
--- 461,473 ----
  	int			i;
  
  	/*
! 	 * Do permissions checks. The check_relation_privileges() checks access
! 	 * permissions for all relations listed in a range table, but does not
! 	 * check anything for other RTE types (function, join, subquery, ...).
! 	 * Function RTEs are checked by init_fcache when the function is prepared
! 	 * for execution. Join, subquery, and special RTEs need no checks.
  	 */
! 	check_relation_privileges(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"
***************
*** 39,44 ****
--- 40,46 ----
  #include "parser/parse_coerce.h"
  #include "parser/parse_relation.h"
  #include "miscadmin.h"
+ #include "nodes/makefuncs.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
***************
*** 2624,2629 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
--- 2626,2633 ----
  	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
  	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
  	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+ 	RangeTblEntry  *pkrte;
+ 	RangeTblEntry  *fkrte;
  	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,2678 ----
  	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?
  	 */
  
! 	/*
! 	 * We built a pair of RTEs of FK/PK relations and columns referenced
! 	 * in the test query to check necessary permission in the common way.
! 	 */
! 	pkrte = makeRangeTblEntry(RTE_RELATION);
! 	pkrte->relid = RelationGetRelid(pk_rel);
! 	pkrte->requiredPerms = ACL_SELECT;
! 
! 	fkrte = makeRangeTblEntry(RTE_RELATION);
! 	fkrte->relid = RelationGetRelid(fk_rel);
! 	fkrte->requiredPerms = ACL_SELECT;
! 
! 	for (i = 0; i < riinfo.nkeys; i++)
! 	{
! 		int		attno;
! 
! 		attno = riinfo.pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
! 		pkrte->selectedCols = bms_add_member(pkrte->selectedCols, attno);
! 
! 		attno = riinfo.fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
! 		fkrte->selectedCols = bms_add_member(fkrte->selectedCols, attno);
! 	}
! 
! 	if (check_relation_privileges(list_make2(fkrte, pkrte), 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,319 ----
  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
+  */
+ extern AclResult check_relation_privileges(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