diff options
Diffstat (limited to '')
-rw-r--r-- | contrib/sepgsql/dml.c | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c new file mode 100644 index 0000000..8c8f6f1 --- /dev/null +++ b/contrib/sepgsql/dml.c @@ -0,0 +1,359 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/dml.c + * + * Routines to handle DML permission checks + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/sysattr.h" +#include "access/tupdesc.h" +#include "catalog/catalog.h" +#include "catalog/dependency.h" +#include "catalog/heap.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_class.h" +#include "catalog/pg_inherits.h" +#include "commands/seclabel.h" +#include "commands/tablecmds.h" +#include "executor/executor.h" +#include "nodes/bitmapset.h" +#include "parser/parsetree.h" +#include "sepgsql.h" +#include "utils/lsyscache.h" +#include "utils/syscache.h" + +/* + * fixup_whole_row_references + * + * When user references a whole-row Var, it is equivalent to referencing + * all the user columns (not system columns). So, we need to fix up the + * given bitmapset, if it contains a whole-row reference. + */ +static Bitmapset * +fixup_whole_row_references(Oid relOid, Bitmapset *columns) +{ + Bitmapset *result; + HeapTuple tuple; + AttrNumber natts; + AttrNumber attno; + int index; + + /* if no whole-row references, nothing to do */ + index = InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber; + if (!bms_is_member(index, columns)) + return columns; + + /* obtain number of attributes */ + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relOid); + natts = ((Form_pg_class) GETSTRUCT(tuple))->relnatts; + ReleaseSysCache(tuple); + + /* remove bit 0 from column set, add in all the non-dropped columns */ + result = bms_copy(columns); + result = bms_del_member(result, index); + + for (attno = 1; attno <= natts; attno++) + { + tuple = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relOid), + Int16GetDatum(attno)); + if (!HeapTupleIsValid(tuple)) + continue; /* unexpected case, should we error? */ + + if (!((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped) + { + index = attno - FirstLowInvalidHeapAttributeNumber; + result = bms_add_member(result, index); + } + + ReleaseSysCache(tuple); + } + return result; +} + +/* + * fixup_inherited_columns + * + * When user is querying on a table with children, it implicitly accesses + * child tables also. So, we also need to check security label of child + * tables and columns, but there is no guarantee attribute numbers are + * same between the parent and children. + * It returns a bitmapset which contains attribute number of the child + * table based on the given bitmapset of the parent. + */ +static Bitmapset * +fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns) +{ + Bitmapset *result = NULL; + int index; + + /* + * obviously, no need to do anything here + */ + if (parentId == childId) + return columns; + + index = -1; + while ((index = bms_next_member(columns, index)) >= 0) + { + /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */ + AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber; + char *attname; + + /* + * whole-row-reference shall be fixed-up later + */ + if (attno == InvalidAttrNumber) + { + result = bms_add_member(result, index); + continue; + } + + attname = get_attname(parentId, attno, false); + attno = get_attnum(childId, attname); + if (attno == InvalidAttrNumber) + elog(ERROR, "cache lookup failed for attribute %s of relation %u", + attname, childId); + + result = bms_add_member(result, + attno - FirstLowInvalidHeapAttributeNumber); + + pfree(attname); + } + + return result; +} + +/* + * check_relation_privileges + * + * It actually checks required permissions on a certain relation + * and its columns. + */ +static bool +check_relation_privileges(Oid relOid, + Bitmapset *selected, + Bitmapset *inserted, + Bitmapset *updated, + uint32 required, + bool abort_on_violation) +{ + ObjectAddress object; + char *audit_name; + Bitmapset *columns; + int index; + char relkind = get_rel_relkind(relOid); + bool result = true; + + /* + * Hardwired Policies: SE-PostgreSQL enforces - clients cannot modify + * system catalogs using DMLs - clients cannot reference/modify toast + * relations using DMLs + */ + if (sepgsql_getenforce() > 0) + { + if ((required & (SEPG_DB_TABLE__UPDATE | + SEPG_DB_TABLE__INSERT | + SEPG_DB_TABLE__DELETE)) != 0 && + IsCatalogRelationOid(relOid)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: hardwired security policy violation"))); + + if (relkind == RELKIND_TOASTVALUE) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: hardwired security policy violation"))); + } + + /* + * Check permissions on the relation + */ + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = 0; + audit_name = getObjectIdentity(&object, false); + switch (relkind) + { + case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: + result = sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_TABLE, + required, + audit_name, + abort_on_violation); + break; + + case RELKIND_SEQUENCE: + Assert((required & ~SEPG_DB_TABLE__SELECT) == 0); + + if (required & SEPG_DB_TABLE__SELECT) + result = sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SEQUENCE, + SEPG_DB_SEQUENCE__GET_VALUE, + audit_name, + abort_on_violation); + break; + + case RELKIND_VIEW: + result = sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_VIEW, + SEPG_DB_VIEW__EXPAND, + audit_name, + abort_on_violation); + break; + + default: + /* nothing to be checked */ + break; + } + pfree(audit_name); + + /* + * Only columns owned by relations shall be checked + */ + if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE) + return true; + + /* + * Check permissions on the columns + */ + selected = fixup_whole_row_references(relOid, selected); + inserted = fixup_whole_row_references(relOid, inserted); + updated = fixup_whole_row_references(relOid, updated); + columns = bms_union(selected, bms_union(inserted, updated)); + + index = -1; + while ((index = bms_next_member(columns, index)) >= 0) + { + AttrNumber attnum; + uint32 column_perms = 0; + + if (bms_is_member(index, selected)) + column_perms |= SEPG_DB_COLUMN__SELECT; + if (bms_is_member(index, inserted)) + { + if (required & SEPG_DB_TABLE__INSERT) + column_perms |= SEPG_DB_COLUMN__INSERT; + } + if (bms_is_member(index, updated)) + { + if (required & SEPG_DB_TABLE__UPDATE) + column_perms |= SEPG_DB_COLUMN__UPDATE; + } + if (column_perms == 0) + continue; + + /* obtain column's permission */ + attnum = index + FirstLowInvalidHeapAttributeNumber; + + object.classId = RelationRelationId; + object.objectId = relOid; + object.objectSubId = attnum; + audit_name = getObjectDescription(&object, false); + + result = sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_COLUMN, + column_perms, + audit_name, + abort_on_violation); + pfree(audit_name); + + if (!result) + return result; + } + return true; +} + +/* + * sepgsql_dml_privileges + * + * Entrypoint of the DML permission checks + */ +bool +sepgsql_dml_privileges(List *rangeTbls, List *rteperminfos, + bool abort_on_violation) +{ + ListCell *lr; + + foreach(lr, rteperminfos) + { + RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr); + uint32 required = 0; + List *tableIds; + ListCell *li; + + /* + * Find out required permissions + */ + if (perminfo->requiredPerms & ACL_SELECT) + required |= SEPG_DB_TABLE__SELECT; + if (perminfo->requiredPerms & ACL_INSERT) + required |= SEPG_DB_TABLE__INSERT; + if (perminfo->requiredPerms & ACL_UPDATE) + { + if (!bms_is_empty(perminfo->updatedCols)) + required |= SEPG_DB_TABLE__UPDATE; + else + required |= SEPG_DB_TABLE__LOCK; + } + if (perminfo->requiredPerms & ACL_DELETE) + required |= SEPG_DB_TABLE__DELETE; + + /* + * Skip, if nothing to be checked + */ + if (required == 0) + continue; + + /* + * If this RangeTblEntry is also supposed to reference inherited + * tables, we need to check security label of the child tables. So, we + * expand rte->relid into list of OIDs of inheritance hierarchy, then + * checker routine will be invoked for each relations. + */ + if (!perminfo->inh) + tableIds = list_make1_oid(perminfo->relid); + else + tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL); + + foreach(li, tableIds) + { + Oid tableOid = lfirst_oid(li); + Bitmapset *selectedCols; + Bitmapset *insertedCols; + Bitmapset *updatedCols; + + /* + * child table has different attribute numbers, so we need to fix + * up them. + */ + selectedCols = fixup_inherited_columns(perminfo->relid, tableOid, + perminfo->selectedCols); + insertedCols = fixup_inherited_columns(perminfo->relid, tableOid, + perminfo->insertedCols); + updatedCols = fixup_inherited_columns(perminfo->relid, tableOid, + perminfo->updatedCols); + + /* + * check permissions on individual tables + */ + if (!check_relation_privileges(tableOid, + selectedCols, + insertedCols, + updatedCols, + required, abort_on_violation)) + return false; + } + list_free(tableIds); + } + return true; +} |