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 ([email protected]) 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 <[email protected]>
*** 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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers