/* ------------------------------------------------------------------------- * * contrib/sepgsql/dml.c * * Routines to handle DML permission checks * * Copyright (c) 2010-2019, 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/heap.h" #include "catalog/dependency.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 "utils/lsyscache.h" #include "utils/syscache.h" #include "sepgsql.h" /* * fixup_whole_row_references * * When user reference a whole of row, it is equivalent to reference to * all the user columns (not system columns). So, we need to fix up the * given bitmapset, if it contains a whole of the 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 of row references, do not anything */ 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); /* fix up the given 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; if (((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped) continue; 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 here is no guarantee attribute numbers are * same between the parent ans 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) { Oid relnamespace = get_rel_namespace(relOid); if (IsSystemNamespace(relnamespace) && (required & (SEPG_DB_TABLE__UPDATE | SEPG_DB_TABLE__INSERT | SEPG_DB_TABLE__DELETE)) != 0) 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); 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)); while ((index = bms_first_member(columns)) >= 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); 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 *rangeTabls, bool abort_on_violation) { ListCell *lr; foreach(lr, rangeTabls) { RangeTblEntry *rte = lfirst(lr); uint32 required = 0; List *tableIds; ListCell *li; /* * Only regular relations shall be checked */ if (rte->rtekind != RTE_RELATION) continue; /* * Find out required permissions */ if (rte->requiredPerms & ACL_SELECT) required |= SEPG_DB_TABLE__SELECT; if (rte->requiredPerms & ACL_INSERT) required |= SEPG_DB_TABLE__INSERT; if (rte->requiredPerms & ACL_UPDATE) { if (!bms_is_empty(rte->updatedCols)) required |= SEPG_DB_TABLE__UPDATE; else required |= SEPG_DB_TABLE__LOCK; } if (rte->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 (!rte->inh) tableIds = list_make1_oid(rte->relid); else tableIds = find_all_inheritors(rte->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(rte->relid, tableOid, rte->selectedCols); insertedCols = fixup_inherited_columns(rte->relid, tableOid, rte->insertedCols); updatedCols = fixup_inherited_columns(rte->relid, tableOid, rte->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; }