(2010/05/21 1:14), Robert Haas wrote:
> In yesterday's development meeting, we talked about the possibility of
> a basic SE-PostgreSQL implementation that checks permissions only for
> DML.  Greg Smith offered the opinion that this could provide much of
> the benefit of SE-PostgreSQL for many users, while being much simpler.
>   In fact, SE-PostgreSQL would need to get control in just one place:
> ExecCheckRTPerms.  This morning, Stephen Frost and I worked up a quick
> patch showing how we could add a hook here to let a hypothetical
> SE-PostgreSQL module get control in the relevant place.  The attached
> patch also includes a toy contrib module showing how it could be used
> to enforce arbitrary security policy.
> 
> I don't think that this by itself would be quite enough framework for
> a minimal SE-PostgreSQL implementation - for that, you'd probably need
> an object-labeling facility in core which SE-PostgreSQL could leverage
> - or else some other way to determine which the label associated with
> a given object - but I think that plus this would be enough.

I'd like to point out two more points are necessary to be considered
for DML permission checks in addition to ExecCheckRTPerms().

* DoCopy()

Although DoCopy() is called from standard_ProcessUtility(), it performs
as DML statement, rather than DDL. It check ACL_SELECT or ACL_INSERT on
the copied table or attributes, similar to what ExecCheckRTEPerms() doing.

* RI_Initial_Check()

RI_Initial_Check() is a function called on ALTER TABLE command to add FK
constraints between two relations. The permission to execute this ALTER TABLE
command itself is checked on ATPrepCmd() and ATAddForeignKeyConstraint(),
so it does not affect anything on the DML permission reworks.

When we add a new FK constraint, both of the existing FK and PK relations have
to satify the new constraint. So, RI_Initial_Check() tries to check whether the
PK relation has corresponding tuples to FK relation, or not.
Then, it tries to execute a secondary query using SPI_*() functions, if no
access violations are expected. Otherwise, it scans the FK relation with
per tuple checks sequentionally (see, validateForeignKeyConstraint()), but slow.

If we have an external security provider which will deny accesses on the FK/PK
relation, but the default PG checks allows it, the RI_Initial_Check() tries to
execute secondary SELECT statement, then it raises an access violation error,
although we are already allowed to execute ALTER TABLE statement.

Therefore, we also need to check DML permissions at RI_Initial_Check() to avoid
unexpected access violation error, prior to the secondary query.

BTW, I guess the reason why permissions on attributes are not checked here is
that we missed it at v8.4 development.


The attached patch provides a common checker function of DML, and modifies
ExecCheckRTPerms(), CopyTo() and RI_Initial_Check() to call the checker
function instead of individual ACL checks.

The most part of the checker function is cut & paste from ExecCheckRTEPerms(),
but its arguments are modified for easy invocation from other functions.

  extern bool check_dml_permissions(Oid relOid,
                                    Oid userId,
                                    AclMode requiredPerms,
                                    Bitmapset *selCols,
                                    Bitmapset *modCols,
                                    bool abort);

Thanks,
-- 
KaiGai Kohei <kai...@ak.jp.nec.com>
*** 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"
***************
*** 41,46 ****
--- 42,48 ----
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
+ #include "utils/security.h"
  #include "utils/snapmgr.h"
  
  
***************
*** 725,733 **** DoCopy(const CopyStmt *stmt, const char *queryString)
  	List	   *force_notnull = NIL;
  	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)
  	{
+ 		Bitmapset  *columnsSet = NULL;
+ 		List	   *attnums;
+ 		ListCell   *cur;
+ 
  		Assert(!stmt->query);
  		cstate->queryDesc = NULL;
  
***************
*** 998,1026 **** 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)
  			PreventCommandIfReadOnly("COPY FROM");
--- 1001,1021 ----
  		tupDesc = RelationGetDescr(cstate->rel);
  
  		/* Check relation permissions. */
! 		attnums = CopyGetAttnums(tupDesc, cstate->rel, attnamelist);
! 		foreach (cur, attnums)
  		{
! 			int	attnum = lfirst_int(cur) - FirstLowInvalidHeapAttributeNumber;
  
! 			columnsSet = bms_add_member(columnsSet, attnum);
  		}
  
+ 		if (is_from)
+ 			check_dml_permissions(RelationGetRelid(cstate->rel), GetUserId(),
+ 								  ACL_INSERT, NULL, columnsSet, true);
+ 		else
+ 			check_dml_permissions(RelationGetRelid(cstate->rel), GetUserId(),
+ 								  ACL_SELECT, columnsSet, NULL, true);
+ 
  		/* check read-only transaction */
  		if (XactReadOnly && is_from && !cstate->rel->rd_islocaltemp)
  			PreventCommandIfReadOnly("COPY FROM");
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 54,59 ****
--- 54,60 ----
  #include "utils/acl.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
+ #include "utils/security.h"
  #include "utils/snapmgr.h"
  #include "utils/tqual.h"
  
***************
*** 73,79 **** static void ExecutePlan(EState *estate, PlanState *planstate,
  			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);
--- 74,79 ----
***************
*** 414,569 **** ExecCheckRTPerms(List *rangeTable)
  
  	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);
! 		}
  	}
  }
  
--- 414,452 ----
  
  	foreach(l, rangeTable)
  	{
! 		RangeTblEntry  *rte = (RangeTblEntry *) lfirst(l);
! 		Oid				userId;
  
! 		/*
! 		 * 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)
! 			continue;
  
  		/*
! 		 * No work if requiredPerms is empty.
  		 */
! 		if (rte->requiredPerms == 0)
! 			continue;
  
  		/*
! 		 * 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();
  
  		/*
! 		 * Priv checks to execute the given DML statement
  		 */
! 		check_dml_permissions(rte->relid, userId, rte->requiredPerms,
! 							  rte->selectedCols, rte->modifiedCols, true);
  	}
  }
  
*** a/src/backend/utils/Makefile
--- b/src/backend/utils/Makefile
***************
*** 9,15 **** top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global
  
  OBJS        = fmgrtab.o
! SUBDIRS     = adt cache error fmgr hash init mb misc mmgr resowner sort time
  
  # location of Catalog.pm
  catalogdir  = $(top_srcdir)/src/backend/catalog
--- 9,15 ----
  include $(top_builddir)/src/Makefile.global
  
  OBJS        = fmgrtab.o
! SUBDIRS     = adt cache error fmgr hash init mb misc mmgr resowner security sort time
  
  # location of Catalog.pm
  catalogdir  = $(top_srcdir)/src/backend/catalog
*** 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,50 ****
  #include "parser/parse_coerce.h"
  #include "parser/parse_relation.h"
  #include "miscadmin.h"
- #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/snapmgr.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
--- 40,51 ----
  #include "parser/parse_coerce.h"
  #include "parser/parse_relation.h"
  #include "miscadmin.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
+ #include "utils/security.h"
  #include "utils/snapmgr.h"
  #include "utils/syscache.h"
  #include "utils/tqual.h"
***************
*** 2624,2629 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
--- 2625,2632 ----
  	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;
  	const char *sep;
  	int			i;
  	int			old_work_mem;
***************
*** 2638,2650 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
  	 *
  	 * 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:
  	 *	SELECT fk.keycols FROM ONLY relname fk
--- 2641,2663 ----
  	 *
  	 * XXX are there any other show-stopper conditions to check?
  	 */
! 	ri_FetchConstraintInfo(&riinfo, trigger, fk_rel, false);
! 
! 	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);
! 	}
! 
! 	if (!check_dml_permissions(RelationGetRelid(fk_rel), GetUserId(),
! 							   ACL_SELECT, fkColumns, NULL, false))
  		return false;
! 	if (!check_dml_permissions(RelationGetRelid(pk_rel), GetUserId(),
! 							   ACL_SELECT, pkColumns, NULL, false))
  		return false;
  
  	/*----------
  	 * The query string built is:
  	 *	SELECT fk.keycols FROM ONLY relname fk
*** /dev/null
--- b/src/backend/utils/security/Makefile
***************
*** 0 ****
--- 1,17 ----
+ #-------------------------------------------------------------------------
+ #
+ # Makefile--
+ #    Makefile for backend/utils/security
+ #
+ # IDENTIFICATION
+ #    $PostgreSQL$
+ #
+ #-------------------------------------------------------------------------
+ 
+ subdir = src/backend/utils/security
+ top_builddir = ../../../..
+ include $(top_builddir)/src/Makefile.global
+ 
+ OBJS = dml.o
+ 
+ include $(top_srcdir)/src/backend/common.mk
*** /dev/null
--- b/src/backend/utils/security/dml.c
***************
*** 0 ****
--- 1,179 ----
+ /*
+  * dml.c
+  *
+  * privileges checker functions corresponding to DML statements
+  *
+  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  */
+ #include "postgres.h"
+ 
+ #include "access/sysattr.h"
+ #include "nodes/bitmapset.h"
+ #include "utils/lsyscache.h"
+ #include "utils/security.h"
+ 
+ /* Hooks for plugin to get control */
+ check_dml_permissions_hook_type check_dml_permissions_hook = NULL;
+ 
+ /*
+  * check_dml_permissions
+  *
+  * It checks access permissions to execute DML statement on a certain
+  * relation and attributes.
+  * The caller shall correctly provide OID of the relation and Bitmapset of
+  * the attributes to be accessed, OID of the database role, a bitmask of
+  * required permissions and a behavior hint on access violations.
+  */
+ bool
+ check_dml_permissions(Oid relOid, Oid userId, AclMode requiredPerms,
+ 					  Bitmapset *selCols, Bitmapset *modCols, bool abort)
+ {
+ 	AclMode		relPerms;
+ 	AclMode		remainingPerms;
+ 	Bitmapset  *tmpset;
+ 	int			col;
+ 	bool		retval = true;
+ 
+ 	/*
+ 	 * 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)
+ 				return false;
+ 
+ 			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(selCols))
+ 			{
+ 				if (pg_attribute_aclcheck_all(relOid, userId, ACL_SELECT,
+ 											  ACLMASK_ANY) != ACLCHECK_OK)
+ 				{
+ 					if (!abort)
+ 						return false;
+ 
+ 					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 								   get_rel_name(relOid));
+ 				}
+ 			}
+ 
+ 			tmpset = bms_copy(selCols);
+ 			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)
+ 							return false;
+ 
+ 						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 									   get_rel_name(relOid));
+ 					}
+ 				}
+ 				else
+ 				{
+ 					if (pg_attribute_aclcheck(relOid, col, userId, ACL_SELECT)
+ 						!= ACLCHECK_OK)
+ 					{
+ 						if (!abort)
+ 							return false;
+ 
+ 						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(modCols))
+ 			{
+ 				if (pg_attribute_aclcheck_all(relOid, userId, remainingPerms,
+ 											  ACLMASK_ANY) != ACLCHECK_OK)
+ 				{
+ 					if (!abort)
+ 						return false;
+ 
+ 					aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 								   get_rel_name(relOid));
+ 				}
+ 			}
+ 
+ 			tmpset = bms_copy(modCols);
+ 			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)
+ 							return false;
+ 
+ 						aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS,
+ 									   get_rel_name(relOid));
+ 					}
+ 				}
+ 			}
+ 			bms_free(tmpset);
+ 		}
+ 	}
+ 
+ 	if (check_dml_permissions_hook)
+ 		retval = (*check_dml_permissions_hook)(relOid, userId, requiredPerms,
+ 											   selCols, modCols, abort);
+ 
+ 	return retval;
+ }
*** /dev/null
--- b/src/include/utils/security.h
***************
*** 0 ****
--- 1,27 ----
+ /*
+  * security.h
+  *	Definition of access control checker functions
+  *
+  * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  */
+ #ifndef SECURITY_H
+ #define SECURITY_H
+ 
+ #include "utils/acl.h"
+ 
+ /*
+  * checker functions corresponding to DML statements
+  */
+ extern bool check_dml_permissions(Oid relOid,
+ 								  Oid userId,
+ 								  AclMode requiredPerms,
+ 								  Bitmapset *selCols,
+ 								  Bitmapset *modCols,
+ 								  bool abort);
+ 
+ typedef bool (*check_dml_permissions_hook_type)
+ 				(Oid, Oid, AclMode, Bitmapset *, Bitmapset *, bool);
+ extern PGDLLIMPORT check_dml_permissions_hook_type check_dml_permissions_hook;
+ 
+ #endif
-- 
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