/* ------------------------------------------------------------------------- * * contrib/sepgsql/label.c * * Routines to support SELinux labels (security context) * * Copyright (c) 2010-2011, PostgreSQL Global Development Group * * ------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" #include "access/genam.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/pg_attribute.h" #include "catalog/pg_class.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "commands/dbcommands.h" #include "commands/seclabel.h" #include "libpq/libpq-be.h" #include "miscadmin.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/tqual.h" #include "sepgsql.h" #include /* * client_label * * security label of the client process */ static char *client_label = NULL; char * sepgsql_get_client_label(void) { return client_label; } char * sepgsql_set_client_label(char *new_label) { char *old_label = client_label; client_label = new_label; return old_label; } /* * sepgsql_get_label * * It returns a security context of the specified database object. * If unlabeled or incorrectly labeled, the system "unlabeled" label * shall be returned. */ char * sepgsql_get_label(Oid classId, Oid objectId, int32 subId) { ObjectAddress object; char *label; object.classId = classId; object.objectId = objectId; object.objectSubId = subId; label = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG); if (!label || security_check_context_raw((security_context_t)label)) { security_context_t unlabeled; if (security_get_initial_context_raw("unlabeled", &unlabeled) < 0) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("selinux: unable to get initial security label"))); PG_TRY(); { label = pstrdup(unlabeled); } PG_CATCH(); { freecon(unlabeled); PG_RE_THROW(); } PG_END_TRY(); freecon(unlabeled); } return label; } /* * sepgsql_object_relabel * * An entrypoint of SECURITY LABEL statement */ void sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel) { /* * validate format of the supplied security label, * if it is security context of selinux. */ if (seclabel && security_check_context_raw((security_context_t) seclabel) < 0) ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("invalid security label: \"%s\"", seclabel))); /* * Do actual permission checks for each object classes */ switch (object->classId) { case NamespaceRelationId: sepgsql_schema_relabel(object->objectId, seclabel); break; case RelationRelationId: if (object->objectSubId == 0) sepgsql_relation_relabel(object->objectId, seclabel); else sepgsql_attribute_relabel(object->objectId, object->objectSubId, seclabel); break; case ProcedureRelationId: sepgsql_proc_relabel(object->objectId, seclabel); break; default: elog(ERROR, "unsupported object type: %u", object->classId); break; } } /* * TEXT sepgsql_getcon(VOID) * * It returns the security label of the client. */ PG_FUNCTION_INFO_V1(sepgsql_getcon); Datum sepgsql_getcon(PG_FUNCTION_ARGS) { char *client_label; if (!sepgsql_is_enabled()) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SELinux: now disabled"))); client_label = sepgsql_get_client_label(); PG_RETURN_POINTER(cstring_to_text(client_label)); } /* * TEXT sepgsql_mcstrans_in(TEXT) * * It translate the given qualified MLS/MCS range into raw format * when mcstrans daemon is working. */ PG_FUNCTION_INFO_V1(sepgsql_mcstrans_in); Datum sepgsql_mcstrans_in(PG_FUNCTION_ARGS) { text *label = PG_GETARG_TEXT_P(0); char *raw_label; char *result; if (!sepgsql_is_enabled()) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SELinux: now disabled"))); if (selinux_trans_to_raw_context(text_to_cstring(label), &raw_label) < 0) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: internal error on mcstrans"))); PG_TRY(); { result = pstrdup(raw_label); } PG_CATCH(); { freecon(raw_label); PG_RE_THROW(); } PG_END_TRY(); freecon(raw_label); PG_RETURN_POINTER(cstring_to_text(result)); } /* * TEXT sepgsql_mcstrans_out(TEXT) * * It translate the given raw MLS/MCS range into qualified format * when mcstrans daemon is working. */ PG_FUNCTION_INFO_V1(sepgsql_mcstrans_out); Datum sepgsql_mcstrans_out(PG_FUNCTION_ARGS) { text *label = PG_GETARG_TEXT_P(0); char *qual_label; char *result; if (!sepgsql_is_enabled()) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SELinux: now disabled"))); if (selinux_raw_to_trans_context(text_to_cstring(label), &qual_label) < 0) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: internal error on mcstrans"))); PG_TRY(); { result = pstrdup(qual_label); } PG_CATCH(); { freecon(qual_label); PG_RE_THROW(); } PG_END_TRY(); freecon(qual_label); PG_RETURN_POINTER(cstring_to_text(result)); } /* * exec_object_restorecon * * This routine is a helper called by sepgsql_restorecon; it set up * initial security labels of database objects within the supplied * catalog OID. */ static void exec_object_restorecon(struct selabel_handle *sehnd, Oid catalogId) { Relation rel; SysScanDesc sscan; HeapTuple tuple; char *database_name = get_database_name(MyDatabaseId); char *namespace_name; Oid namespace_id; char *relation_name; /* * Open the target catalog. We don't want to allow writable * accesses by other session during initial labeling. */ rel = heap_open(catalogId, AccessShareLock); sscan = systable_beginscan(rel, InvalidOid, false, SnapshotNow, 0, NULL); while (HeapTupleIsValid(tuple = systable_getnext(sscan))) { Form_pg_namespace nspForm; Form_pg_class relForm; Form_pg_attribute attForm; Form_pg_proc proForm; char objname[NAMEDATALEN * 4 + 10]; int objtype = 1234; ObjectAddress object; security_context_t context; /* * The way to determine object name depends on object classes. * So, any branches set up `objtype', `objname' and `object' here. */ switch (catalogId) { case NamespaceRelationId: nspForm = (Form_pg_namespace) GETSTRUCT(tuple); objtype = SELABEL_DB_SCHEMA; snprintf(objname, sizeof(objname), "%s.%s", database_name, NameStr(nspForm->nspname)); object.classId = NamespaceRelationId; object.objectId = HeapTupleGetOid(tuple); object.objectSubId = 0; break; case RelationRelationId: relForm = (Form_pg_class) GETSTRUCT(tuple); if (relForm->relkind == RELKIND_RELATION) objtype = SELABEL_DB_TABLE; else if (relForm->relkind == RELKIND_SEQUENCE) objtype = SELABEL_DB_SEQUENCE; else if (relForm->relkind == RELKIND_VIEW) objtype = SELABEL_DB_VIEW; else continue; /* no need to assign security label */ namespace_name = get_namespace_name(relForm->relnamespace); snprintf(objname, sizeof(objname), "%s.%s.%s", database_name, namespace_name, NameStr(relForm->relname)); pfree(namespace_name); object.classId = RelationRelationId; object.objectId = HeapTupleGetOid(tuple); object.objectSubId = 0; break; case AttributeRelationId: attForm = (Form_pg_attribute) GETSTRUCT(tuple); if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION) continue; /* no need to assign security label */ objtype = SELABEL_DB_COLUMN; namespace_id = get_rel_namespace(attForm->attrelid); namespace_name = get_namespace_name(namespace_id); relation_name = get_rel_name(attForm->attrelid); snprintf(objname, sizeof(objname), "%s.%s.%s.%s", database_name, namespace_name, relation_name, NameStr(attForm->attname)); pfree(relation_name); pfree(namespace_name); object.classId = RelationRelationId; object.objectId = attForm->attrelid; object.objectSubId = attForm->attnum; break; case ProcedureRelationId: proForm = (Form_pg_proc) GETSTRUCT(tuple); objtype = SELABEL_DB_PROCEDURE; namespace_name = get_namespace_name(proForm->pronamespace); snprintf(objname, sizeof(objname), "%s.%s.%s", database_name, namespace_name, NameStr(proForm->proname)); pfree(namespace_name); object.classId = ProcedureRelationId; object.objectId = HeapTupleGetOid(tuple); object.objectSubId = 0; break; default: elog(ERROR, "Bug? %u is not supported to set initial labels", catalogId); break; } if (selabel_lookup_raw(sehnd, &context, objname, objtype) == 0) { PG_TRY(); { /* * Check SELinux permission to relabel the fetched object, * then do the actual relabeling. */ sepgsql_object_relabel(&object, context); SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, context); } PG_CATCH(); { freecon(context); PG_RE_THROW(); } PG_END_TRY(); freecon(context); } else if (errno == ENOENT) ereport(WARNING, (errmsg("no valid initial label on %s (type=%d), skipped", objname, objtype))); else ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("libselinux: internal error"))); } systable_endscan(sscan); heap_close(rel, NoLock); } /* * BOOL sepgsql_restorecon(TEXT specfile) * * This function tries to assign initial security labels on all the object * within the current database, according to the system setting. * It is typically invoked by sepgsql-install script just after initdb, to * assign initial security labels. * * If @specfile is not NULL, it uses explicitly specified specfile, instead * of the system default. */ PG_FUNCTION_INFO_V1(sepgsql_restorecon); Datum sepgsql_restorecon(PG_FUNCTION_ARGS) { struct selabel_handle *sehnd; struct selinux_opt seopts; /* * SELinux has to be enabled on the running platform. */ if (!sepgsql_is_enabled()) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SELinux: now disabled"))); /* * Check DAC permission. Only superuser can set up initial * security labels, like root-user in filesystems */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to restore initial contexts"))); /* * Open selabel_lookup(3) stuff. It provides a set of mapping * between an initial security label and object class/name due * to the system setting. */ if (PG_ARGISNULL(0)) { seopts.type = SELABEL_OPT_UNUSED; seopts.value = NULL; } else { seopts.type = SELABEL_OPT_PATH; seopts.value = TextDatumGetCString(PG_GETARG_DATUM(0)); } sehnd = selabel_open(SELABEL_CTX_DB, &seopts, 1); if (!sehnd) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux internal error"))); PG_TRY(); { /* * Right now, we have no support labeling on the shared * database objects, such as database, role, or tablespace. */ exec_object_restorecon(sehnd, NamespaceRelationId); exec_object_restorecon(sehnd, RelationRelationId); exec_object_restorecon(sehnd, AttributeRelationId); exec_object_restorecon(sehnd, ProcedureRelationId); } PG_CATCH(); { selabel_close(sehnd); PG_RE_THROW(); } PG_END_TRY(); selabel_close(sehnd); PG_RETURN_BOOL(true); }