/* ------------------------------------------------------------------------- * * seclabel.c * routines to support security label feature. * * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * ------------------------------------------------------------------------- */ #include "postgres.h" #include "access/genam.h" #include "access/heapam.h" #include "catalog/catalog.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_seclabel.h" #include "commands/seclabel.h" #include "miscadmin.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/tqual.h" /* * For most object types, the permissions-checking logic is simple enough * that it makes sense to just include it in CommentObject(). However, * attributes require a bit more checking. */ static void CheckAttributeSecLabel(Relation relation); typedef struct { const char *provider_name; check_object_relabel_type hook; } LabelProvider; static List *label_provider_list = NIL; /* * ExecSecLabelStmt -- * * Apply a security label to a database object. */ void ExecSecLabelStmt(SecLabelStmt *stmt) { LabelProvider *provider = NULL; ObjectAddress address; Relation relation; ListCell *lc; /* * Find the named label provider, or if none specified, check whether * there's exactly one, and if so use it. */ if (stmt->provider == NULL) { if (label_provider_list == NIL) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("no security label providers have been loaded"))); if (lnext(list_head(label_provider_list)) != NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("must specify provider when multiple security label providers have been loaded"))); provider = (LabelProvider *) linitial(label_provider_list); } else { foreach (lc, label_provider_list) { LabelProvider *lp = lfirst(lc); if (strcmp(stmt->provider, lp->provider_name) == 0) { provider = lp; break; } } if (provider == NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("security label provider \"%s\" is not loaded", stmt->provider))); } /* * Translate the parser representation which identifies this object * into an ObjectAddress. get_object_address() will throw an error if * the object does not exist, and will also acquire a lock on the * target to guard against concurrent modifications. */ address = get_object_address(stmt->objtype, stmt->objname, stmt->objargs, &relation, ShareUpdateExclusiveLock); /* Privilege and integrity checks. */ switch (stmt->objtype) { case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); break; case OBJECT_COLUMN: CheckAttributeSecLabel(relation); break; case OBJECT_TYPE: if (!pg_type_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TYPE, format_type_be(address.objectId)); break; case OBJECT_AGGREGATE: case OBJECT_FUNCTION: if (!pg_proc_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->objname)); break; case OBJECT_SCHEMA: if (!pg_namespace_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_NAMESPACE, strVal(linitial(stmt->objname))); break; case OBJECT_LANGUAGE: if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to comment on procedural language"))); break; case OBJECT_LARGEOBJECT: if (!pg_largeobject_ownercheck(address.objectId, GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be owner of large object %u", address.objectId))); break; default: elog(ERROR, "unrecognized object type: %d", (int) stmt->objtype); } /* Provider gets control here, may throw ERROR to veto new label. */ (*provider->hook)(&address, stmt->label); /* Apply new label. */ SetSecurityLabel(&address, provider->provider_name, stmt->label); /* * If get_object_address() opened the relation for us, we close it to keep * the reference count correct - but we retain any locks acquired by * get_object_address() until commit time, to guard against concurrent * activity. */ if (relation != NULL) relation_close(relation, NoLock); } /* * GetSecurityLabel returns the security label for a database object for a * given provider, or NULL if there is no such label. */ char * GetSecurityLabel(const ObjectAddress *object, const char *provider) { Relation pg_seclabel; ScanKeyData keys[4]; SysScanDesc scan; HeapTuple tuple; Datum datum; bool isnull; char *seclabel = NULL; Assert(!IsSharedRelation(object->classId)); ScanKeyInit(&keys[0], Anum_pg_seclabel_objoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); ScanKeyInit(&keys[1], Anum_pg_seclabel_classoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->classId)); ScanKeyInit(&keys[2], Anum_pg_seclabel_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(object->objectSubId)); ScanKeyInit(&keys[3], Anum_pg_seclabel_provider, BTEqualStrategyNumber, F_TEXTEQ, CStringGetTextDatum(provider)); pg_seclabel = heap_open(SecLabelRelationId, AccessShareLock); scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, SnapshotNow, 4, keys); tuple = systable_getnext(scan); if (HeapTupleIsValid(tuple)) { datum = heap_getattr(tuple, Anum_pg_seclabel_label, RelationGetDescr(pg_seclabel), &isnull); if (!isnull) seclabel = TextDatumGetCString(datum); } systable_endscan(scan); heap_close(pg_seclabel, AccessShareLock); return seclabel; } /* * SetSecurityLabel attempts to set the security label for the specified * provider on the specified object to the given value. NULL means that any * any existing label should be deleted. */ void SetSecurityLabel(const ObjectAddress *object, const char *provider, const char *label) { Relation pg_seclabel; ScanKeyData keys[4]; SysScanDesc scan; HeapTuple oldtup; HeapTuple newtup = NULL; Datum values[Natts_pg_seclabel]; bool nulls[Natts_pg_seclabel]; bool replaces[Natts_pg_seclabel]; /* Security labels on shared objects are not supported. */ Assert(!IsSharedRelation(object->classId)); /* Prepare to form or update a tuple, if necessary. */ memset(nulls, false, sizeof(nulls)); memset(replaces, false, sizeof(replaces)); values[Anum_pg_seclabel_objoid - 1] = ObjectIdGetDatum(object->objectId); values[Anum_pg_seclabel_classoid - 1] = ObjectIdGetDatum(object->classId); values[Anum_pg_seclabel_objsubid - 1] = Int32GetDatum(object->objectSubId); values[Anum_pg_seclabel_provider - 1] = CStringGetTextDatum(provider); if (label != NULL) values[Anum_pg_seclabel_label - 1] = CStringGetTextDatum(label); /* Use the index to search for a matching old tuple */ ScanKeyInit(&keys[0], Anum_pg_seclabel_objoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); ScanKeyInit(&keys[1], Anum_pg_seclabel_classoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->classId)); ScanKeyInit(&keys[2], Anum_pg_seclabel_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(object->objectSubId)); ScanKeyInit(&keys[3], Anum_pg_seclabel_provider, BTEqualStrategyNumber, F_TEXTEQ, CStringGetTextDatum(provider)); pg_seclabel = heap_open(SecLabelRelationId, RowExclusiveLock); scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, SnapshotNow, 4, keys); oldtup = systable_getnext(scan); if (HeapTupleIsValid(oldtup)) { if (label == NULL) simple_heap_delete(pg_seclabel, &oldtup->t_self); else { replaces[Anum_pg_seclabel_label - 1] = true; newtup = heap_modify_tuple(oldtup, RelationGetDescr(pg_seclabel), values, nulls, replaces); simple_heap_update(pg_seclabel, &oldtup->t_self, newtup); } } systable_endscan(scan); /* If we didn't find an old tuple, insert a new one */ if (newtup == NULL && label != NULL) { newtup = heap_form_tuple(RelationGetDescr(pg_seclabel), values, nulls); simple_heap_insert(pg_seclabel, newtup); } /* Update indexes, if necessary */ if (newtup != NULL) { CatalogUpdateIndexes(pg_seclabel, newtup); heap_freetuple(newtup); } heap_close(pg_seclabel, RowExclusiveLock); } /* * DeleteSecurityLabel removes all security labels for an object (and any * sub-objects, if applicable). */ void DeleteSecurityLabel(const ObjectAddress *object) { Relation pg_seclabel; ScanKeyData skey[3]; SysScanDesc scan; HeapTuple oldtup; int nkeys; /* Security labels on shared objects are not supported. */ if (IsSharedRelation(object->classId)) return; ScanKeyInit(&skey[0], Anum_pg_seclabel_objoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); ScanKeyInit(&skey[1], Anum_pg_seclabel_classoid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->classId)); if (object->objectSubId != 0) { ScanKeyInit(&skey[2], Anum_pg_seclabel_objsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(object->objectSubId)); nkeys = 3; } else nkeys = 2; pg_seclabel = heap_open(SecLabelRelationId, RowExclusiveLock); scan = systable_beginscan(pg_seclabel, SecLabelObjectIndexId, true, SnapshotNow, nkeys, skey); while (HeapTupleIsValid(oldtup = systable_getnext(scan))) simple_heap_delete(pg_seclabel, &oldtup->t_self); systable_endscan(scan); heap_close(pg_seclabel, RowExclusiveLock); } /* * Check whether the user is allowed to comment on an attribute of the * specified relation. */ static void CheckAttributeSecLabel(Relation relation) { if (!pg_class_ownercheck(RelationGetRelid(relation), GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); /* * Allow security labels only on columns of tables, views, and composite * types (which are the only relkinds for which pg_dump will dump labels). */ if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_VIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, or composite type", RelationGetRelationName(relation)))); } void register_label_provider(const char *provider_name, check_object_relabel_type hook) { LabelProvider *provider; MemoryContext oldcxt; oldcxt = MemoryContextSwitchTo(TopMemoryContext); provider = palloc(sizeof(LabelProvider)); provider->provider_name = pstrdup(provider_name); provider->hook = hook; label_provider_list = lappend(label_provider_list, provider); MemoryContextSwitchTo(oldcxt); }