dml_reworks_kaigai.1.patch
text/x-patch
Filename: dml_reworks_kaigai.1.patch
Type: text/x-patch
Part: 0
Message:
Re: ExecutorCheckPerms() hook
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
| File | + | − |
|---|---|---|
| src/backend/commands/copy.c | 17 | 0 |
| src/backend/executor/execMain.c | 23 | 0 |
| src/backend/utils/adt/ri_triggers.c | 18 | 1 |
| src/backend/utils/Makefile | 1 | 0 |
| src/backend/utils/security/dml.c | 179 | 0 |
| src/backend/utils/security/Makefile | 17 | 0 |
| src/include/utils/security.h | 27 | 0 |
*** 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