postgresql/contrib/sepgsql/relation.c

774 lines
19 KiB
C

/* -------------------------------------------------------------------------
*
* contrib/sepgsql/relation.c
*
* Routines corresponding to relation/attribute objects
*
* Copyright (c) 2010-2022, PostgreSQL Global Development Group
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/dependency.h"
#include "catalog/pg_attribute.h"
#include "catalog/pg_class.h"
#include "catalog/pg_namespace.h"
#include "commands/seclabel.h"
#include "lib/stringinfo.h"
#include "sepgsql.h"
#include "utils/builtins.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
static void sepgsql_index_modify(Oid indexOid);
/*
* sepgsql_attribute_post_create
*
* This routine assigns a default security label on a newly defined
* column, using ALTER TABLE ... ADD COLUMN.
* Note that this routine is not invoked in the case of CREATE TABLE,
* although it also defines columns in addition to table.
*/
void
sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
{
Relation rel;
ScanKeyData skey[2];
SysScanDesc sscan;
HeapTuple tuple;
char *scontext;
char *tcontext;
char *ncontext;
ObjectAddress object;
Form_pg_attribute attForm;
StringInfoData audit_name;
char relkind = get_rel_relkind(relOid);
/*
* Only attributes within regular relations or partition relations have
* individual security labels.
*/
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
return;
/*
* Compute a default security label of the new column underlying the
* specified relation, and check permission to create it.
*/
rel = table_open(AttributeRelationId, AccessShareLock);
ScanKeyInit(&skey[0],
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relOid));
ScanKeyInit(&skey[1],
Anum_pg_attribute_attnum,
BTEqualStrategyNumber, F_INT2EQ,
Int16GetDatum(attnum));
sscan = systable_beginscan(rel, AttributeRelidNumIndexId, true,
SnapshotSelf, 2, &skey[0]);
tuple = systable_getnext(sscan);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for column %d of relation %u",
attnum, relOid);
attForm = (Form_pg_attribute) GETSTRUCT(tuple);
scontext = sepgsql_get_client_label();
tcontext = sepgsql_get_label(RelationRelationId, relOid, 0);
ncontext = sepgsql_compute_create(scontext, tcontext,
SEPG_CLASS_DB_COLUMN,
NameStr(attForm->attname));
/*
* check db_column:{create} permission
*/
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = 0;
initStringInfo(&audit_name);
appendStringInfo(&audit_name, "%s.%s",
getObjectIdentity(&object, false),
quote_identifier(NameStr(attForm->attname)));
sepgsql_avc_check_perms_label(ncontext,
SEPG_CLASS_DB_COLUMN,
SEPG_DB_COLUMN__CREATE,
audit_name.data,
true);
/*
* Assign the default security label on a new procedure
*/
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = attnum;
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
systable_endscan(sscan);
table_close(rel, AccessShareLock);
pfree(tcontext);
pfree(ncontext);
}
/*
* sepgsql_attribute_drop
*
* It checks privileges to drop the supplied column.
*/
void
sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
{
ObjectAddress object;
char *audit_name;
char relkind = get_rel_relkind(relOid);
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
return;
/*
* check db_column:{drop} permission
*/
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = attnum;
audit_name = getObjectIdentity(&object, false);
sepgsql_avc_check_perms(&object,
SEPG_CLASS_DB_COLUMN,
SEPG_DB_COLUMN__DROP,
audit_name,
true);
pfree(audit_name);
}
/*
* sepgsql_attribute_relabel
*
* It checks privileges to relabel the supplied column
* by the `seclabel'.
*/
void
sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
const char *seclabel)
{
ObjectAddress object;
char *audit_name;
char relkind = get_rel_relkind(relOid);
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot set security label on non-regular columns")));
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = attnum;
audit_name = getObjectIdentity(&object, false);
/*
* check db_column:{setattr relabelfrom} permission
*/
sepgsql_avc_check_perms(&object,
SEPG_CLASS_DB_COLUMN,
SEPG_DB_COLUMN__SETATTR |
SEPG_DB_COLUMN__RELABELFROM,
audit_name,
true);
/*
* check db_column:{relabelto} permission
*/
sepgsql_avc_check_perms_label(seclabel,
SEPG_CLASS_DB_COLUMN,
SEPG_DB_PROCEDURE__RELABELTO,
audit_name,
true);
pfree(audit_name);
}
/*
* sepgsql_attribute_setattr
*
* It checks privileges to alter the supplied column.
*/
void
sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
{
ObjectAddress object;
char *audit_name;
char relkind = get_rel_relkind(relOid);
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
return;
/*
* check db_column:{setattr} permission
*/
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = attnum;
audit_name = getObjectIdentity(&object, false);
sepgsql_avc_check_perms(&object,
SEPG_CLASS_DB_COLUMN,
SEPG_DB_COLUMN__SETATTR,
audit_name,
true);
pfree(audit_name);
}
/*
* sepgsql_relation_post_create
*
* The post creation hook of relation/attribute
*/
void
sepgsql_relation_post_create(Oid relOid)
{
Relation rel;
ScanKeyData skey;
SysScanDesc sscan;
HeapTuple tuple;
Form_pg_class classForm;
ObjectAddress object;
uint16_t tclass;
char *scontext; /* subject */
char *tcontext; /* schema */
char *rcontext; /* relation */
char *ccontext; /* column */
char *nsp_name;
StringInfoData audit_name;
/*
* Fetch catalog record of the new relation. Because pg_class entry is not
* visible right now, we need to scan the catalog using SnapshotSelf.
*/
rel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&skey,
Anum_pg_class_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relOid));
sscan = systable_beginscan(rel, ClassOidIndexId, true,
SnapshotSelf, 1, &skey);
tuple = systable_getnext(sscan);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for relation %u", relOid);
classForm = (Form_pg_class) GETSTRUCT(tuple);
/* ignore indexes on toast tables */
if (classForm->relkind == RELKIND_INDEX &&
classForm->relnamespace == PG_TOAST_NAMESPACE)
goto out;
/*
* check db_schema:{add_name} permission of the namespace
*/
object.classId = NamespaceRelationId;
object.objectId = classForm->relnamespace;
object.objectSubId = 0;
sepgsql_avc_check_perms(&object,
SEPG_CLASS_DB_SCHEMA,
SEPG_DB_SCHEMA__ADD_NAME,
getObjectIdentity(&object, false),
true);
switch (classForm->relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
tclass = SEPG_CLASS_DB_TABLE;
break;
case RELKIND_SEQUENCE:
tclass = SEPG_CLASS_DB_SEQUENCE;
break;
case RELKIND_VIEW:
tclass = SEPG_CLASS_DB_VIEW;
break;
case RELKIND_INDEX:
/* deal with indexes specially; no need for tclass */
sepgsql_index_modify(relOid);
goto out;
default:
/* ignore other relkinds */
goto out;
}
/*
* Compute a default security label when we create a new relation object
* under the specified namespace.
*/
scontext = sepgsql_get_client_label();
tcontext = sepgsql_get_label(NamespaceRelationId,
classForm->relnamespace, 0);
rcontext = sepgsql_compute_create(scontext, tcontext, tclass,
NameStr(classForm->relname));
/*
* check db_xxx:{create} permission
*/
nsp_name = get_namespace_name(classForm->relnamespace);
initStringInfo(&audit_name);
appendStringInfo(&audit_name, "%s.%s",
quote_identifier(nsp_name),
quote_identifier(NameStr(classForm->relname)));
sepgsql_avc_check_perms_label(rcontext,
tclass,
SEPG_DB_DATABASE__CREATE,
audit_name.data,
true);
/*
* Assign the default security label on the new regular or partitioned
* relation.
*/
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = 0;
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext);
/*
* We also assign a default security label on columns of a new table.
*/
if (classForm->relkind == RELKIND_RELATION ||
classForm->relkind == RELKIND_PARTITIONED_TABLE)
{
Relation arel;
ScanKeyData akey;
SysScanDesc ascan;
HeapTuple atup;
Form_pg_attribute attForm;
arel = table_open(AttributeRelationId, AccessShareLock);
ScanKeyInit(&akey,
Anum_pg_attribute_attrelid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relOid));
ascan = systable_beginscan(arel, AttributeRelidNumIndexId, true,
SnapshotSelf, 1, &akey);
while (HeapTupleIsValid(atup = systable_getnext(ascan)))
{
attForm = (Form_pg_attribute) GETSTRUCT(atup);
resetStringInfo(&audit_name);
appendStringInfo(&audit_name, "%s.%s.%s",
quote_identifier(nsp_name),
quote_identifier(NameStr(classForm->relname)),
quote_identifier(NameStr(attForm->attname)));
ccontext = sepgsql_compute_create(scontext,
rcontext,
SEPG_CLASS_DB_COLUMN,
NameStr(attForm->attname));
/*
* check db_column:{create} permission
*/
sepgsql_avc_check_perms_label(ccontext,
SEPG_CLASS_DB_COLUMN,
SEPG_DB_COLUMN__CREATE,
audit_name.data,
true);
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = attForm->attnum;
SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext);
pfree(ccontext);
}
systable_endscan(ascan);
table_close(arel, AccessShareLock);
}
pfree(rcontext);
out:
systable_endscan(sscan);
table_close(rel, AccessShareLock);
}
/*
* sepgsql_relation_drop
*
* It checks privileges to drop the supplied relation.
*/
void
sepgsql_relation_drop(Oid relOid)
{
ObjectAddress object;
char *audit_name;
uint16_t tclass = 0;
char relkind = get_rel_relkind(relOid);
switch (relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
tclass = SEPG_CLASS_DB_TABLE;
break;
case RELKIND_SEQUENCE:
tclass = SEPG_CLASS_DB_SEQUENCE;
break;
case RELKIND_VIEW:
tclass = SEPG_CLASS_DB_VIEW;
break;
case RELKIND_INDEX:
/* ignore indexes on toast tables */
if (get_rel_namespace(relOid) == PG_TOAST_NAMESPACE)
return;
/* other indexes are handled specially below; no need for tclass */
break;
default:
/* ignore other relkinds */
return;
}
/*
* check db_schema:{remove_name} permission
*/
object.classId = NamespaceRelationId;
object.objectId = get_rel_namespace(relOid);
object.objectSubId = 0;
audit_name = getObjectIdentity(&object, false);
sepgsql_avc_check_perms(&object,
SEPG_CLASS_DB_SCHEMA,
SEPG_DB_SCHEMA__REMOVE_NAME,
audit_name,
true);
pfree(audit_name);
/* deal with indexes specially */
if (relkind == RELKIND_INDEX)
{
sepgsql_index_modify(relOid);
return;
}
/*
* check db_table/sequence/view:{drop} permission
*/
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = 0;
audit_name = getObjectIdentity(&object, false);
sepgsql_avc_check_perms(&object,
tclass,
SEPG_DB_TABLE__DROP,
audit_name,
true);
pfree(audit_name);
/*
* check db_column:{drop} permission
*/
if (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)
{
Form_pg_attribute attForm;
CatCList *attrList;
HeapTuple atttup;
int i;
attrList = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(relOid));
for (i = 0; i < attrList->n_members; i++)
{
atttup = &attrList->members[i]->tuple;
attForm = (Form_pg_attribute) GETSTRUCT(atttup);
if (attForm->attisdropped)
continue;
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = attForm->attnum;
audit_name = getObjectIdentity(&object, false);
sepgsql_avc_check_perms(&object,
SEPG_CLASS_DB_COLUMN,
SEPG_DB_COLUMN__DROP,
audit_name,
true);
pfree(audit_name);
}
ReleaseCatCacheList(attrList);
}
}
/*
* sepgsql_relation_truncate
*
* Check privileges to TRUNCATE the supplied relation.
*/
void
sepgsql_relation_truncate(Oid relOid)
{
ObjectAddress object;
char *audit_name;
uint16_t tclass = 0;
char relkind = get_rel_relkind(relOid);
switch (relkind)
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
tclass = SEPG_CLASS_DB_TABLE;
break;
default:
/* ignore other relkinds */
return;
}
/*
* check db_table:{truncate} permission
*/
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = 0;
audit_name = getObjectIdentity(&object, false);
sepgsql_avc_check_perms(&object,
tclass,
SEPG_DB_TABLE__TRUNCATE,
audit_name,
true);
pfree(audit_name);
}
/*
* sepgsql_relation_relabel
*
* It checks privileges to relabel the supplied relation by the `seclabel'.
*/
void
sepgsql_relation_relabel(Oid relOid, const char *seclabel)
{
ObjectAddress object;
char *audit_name;
char relkind = get_rel_relkind(relOid);
uint16_t tclass = 0;
if (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)
tclass = SEPG_CLASS_DB_TABLE;
else if (relkind == RELKIND_SEQUENCE)
tclass = SEPG_CLASS_DB_SEQUENCE;
else if (relkind == RELKIND_VIEW)
tclass = SEPG_CLASS_DB_VIEW;
else
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot set security labels on relations except "
"for tables, sequences or views")));
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = 0;
audit_name = getObjectIdentity(&object, false);
/*
* check db_xxx:{setattr relabelfrom} permission
*/
sepgsql_avc_check_perms(&object,
tclass,
SEPG_DB_TABLE__SETATTR |
SEPG_DB_TABLE__RELABELFROM,
audit_name,
true);
/*
* check db_xxx:{relabelto} permission
*/
sepgsql_avc_check_perms_label(seclabel,
tclass,
SEPG_DB_TABLE__RELABELTO,
audit_name,
true);
pfree(audit_name);
}
/*
* sepgsql_relation_setattr
*
* It checks privileges to set attribute of the supplied relation
*/
void
sepgsql_relation_setattr(Oid relOid)
{
Relation rel;
ScanKeyData skey;
SysScanDesc sscan;
HeapTuple oldtup;
HeapTuple newtup;
Form_pg_class oldform;
Form_pg_class newform;
ObjectAddress object;
char *audit_name;
uint16_t tclass;
switch (get_rel_relkind(relOid))
{
case RELKIND_RELATION:
case RELKIND_PARTITIONED_TABLE:
tclass = SEPG_CLASS_DB_TABLE;
break;
case RELKIND_SEQUENCE:
tclass = SEPG_CLASS_DB_SEQUENCE;
break;
case RELKIND_VIEW:
tclass = SEPG_CLASS_DB_VIEW;
break;
case RELKIND_INDEX:
/* deal with indexes specially */
sepgsql_index_modify(relOid);
return;
default:
/* other relkinds don't need additional work */
return;
}
/*
* Fetch newer catalog
*/
rel = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&skey,
Anum_pg_class_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(relOid));
sscan = systable_beginscan(rel, ClassOidIndexId, true,
SnapshotSelf, 1, &skey);
newtup = systable_getnext(sscan);
if (!HeapTupleIsValid(newtup))
elog(ERROR, "could not find tuple for relation %u", relOid);
newform = (Form_pg_class) GETSTRUCT(newtup);
/*
* Fetch older catalog
*/
oldtup = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
if (!HeapTupleIsValid(oldtup))
elog(ERROR, "cache lookup failed for relation %u", relOid);
oldform = (Form_pg_class) GETSTRUCT(oldtup);
/*
* Does this ALTER command takes operation to namespace?
*/
if (newform->relnamespace != oldform->relnamespace)
{
sepgsql_schema_remove_name(oldform->relnamespace);
sepgsql_schema_add_name(newform->relnamespace);
}
if (strcmp(NameStr(newform->relname), NameStr(oldform->relname)) != 0)
sepgsql_schema_rename(oldform->relnamespace);
/*
* XXX - In the future version, db_tuple:{use} of system catalog entry
* shall be checked, if tablespace configuration is changed.
*/
/*
* check db_xxx:{setattr} permission
*/
object.classId = RelationRelationId;
object.objectId = relOid;
object.objectSubId = 0;
audit_name = getObjectIdentity(&object, false);
sepgsql_avc_check_perms(&object,
tclass,
SEPG_DB_TABLE__SETATTR,
audit_name,
true);
pfree(audit_name);
ReleaseSysCache(oldtup);
systable_endscan(sscan);
table_close(rel, AccessShareLock);
}
/*
* sepgsql_relation_setattr_extra
*
* It checks permission of the relation being referenced by extra attributes,
* such as pg_index entries. Like core PostgreSQL, sepgsql also does not deal
* with such entries as individual "objects", thus, modification of these
* entries shall be considered as setting an attribute of the underlying
* relation.
*/
static void
sepgsql_relation_setattr_extra(Relation catalog,
Oid catindex_id,
Oid extra_oid,
AttrNumber anum_relation_id,
AttrNumber anum_extra_id)
{
ScanKeyData skey;
SysScanDesc sscan;
HeapTuple tuple;
Datum datum;
bool isnull;
ScanKeyInit(&skey, anum_extra_id,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(extra_oid));
sscan = systable_beginscan(catalog, catindex_id, true,
SnapshotSelf, 1, &skey);
tuple = systable_getnext(sscan);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "could not find tuple for object %u in catalog \"%s\"",
extra_oid, RelationGetRelationName(catalog));
datum = heap_getattr(tuple, anum_relation_id,
RelationGetDescr(catalog), &isnull);
Assert(!isnull);
sepgsql_relation_setattr(DatumGetObjectId(datum));
systable_endscan(sscan);
}
/*
* sepgsql_index_modify
* Handle index create, update, drop
*
* Unlike other relation kinds, indexes do not have their own security labels,
* so instead of doing checks directly, treat them as extra attributes of their
* owning tables; so check 'setattr' permissions on the table.
*/
static void
sepgsql_index_modify(Oid indexOid)
{
Relation catalog = table_open(IndexRelationId, AccessShareLock);
/* check db_table:{setattr} permission of the table being indexed */
sepgsql_relation_setattr_extra(catalog,
IndexRelidIndexId,
indexOid,
Anum_pg_index_indrelid,
Anum_pg_index_indexrelid);
table_close(catalog, AccessShareLock);
}