/* ------------------------------------------------------------------------- * * contrib/sepgsql/label.c * * Routines to support SELinux labels (security context) * * Copyright (c) 2010-2019, PostgreSQL Global Development Group * * ------------------------------------------------------------------------- */ #include "postgres.h" #include #include "access/genam.h" #include "access/htup_details.h" #include "access/table.h" #include "access/xact.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_database.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "commands/dbcommands.h" #include "commands/seclabel.h" #include "libpq/auth.h" #include "libpq/libpq-be.h" #include "miscadmin.h" #include "sepgsql.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" /* * Saved hook entries (if stacked) */ static ClientAuthentication_hook_type next_client_auth_hook = NULL; static needs_fmgr_hook_type next_needs_fmgr_hook = NULL; static fmgr_hook_type next_fmgr_hook = NULL; /* * client_label_* * * security label of the database client. Initially the client security label * is equal to client_label_peer, and can be changed by one or more calls to * sepgsql_setcon(), and also be temporarily overridden during execution of a * trusted-procedure. * * sepgsql_setcon() is a transaction-aware operation; a (sub-)transaction * rollback should also rollback the current client security label. Therefore * we use the list client_label_pending of pending_label to keep track of which * labels were set during the (sub-)transactions. */ static char *client_label_peer = NULL; /* set by getpeercon(3) */ static List *client_label_pending = NIL; /* pending list being set by * sepgsql_setcon() */ static char *client_label_committed = NULL; /* set by sepgsql_setcon(), and * already committed */ static char *client_label_func = NULL; /* set by trusted procedure */ typedef struct { SubTransactionId subid; char *label; } pending_label; /* * sepgsql_get_client_label * * Returns the current security label of the client. All code should use this * routine to get the current label, instead of referring to the client_label_* * variables above. */ char * sepgsql_get_client_label(void) { /* trusted procedure client label override */ if (client_label_func) return client_label_func; /* uncommitted sepgsql_setcon() value */ if (client_label_pending) { pending_label *plabel = llast(client_label_pending); if (plabel->label) return plabel->label; } else if (client_label_committed) return client_label_committed; /* set by sepgsql_setcon() committed */ /* default label */ Assert(client_label_peer != NULL); return client_label_peer; } /* * sepgsql_set_client_label * * This routine tries to switch the current security label of the client, and * checks related permissions. The supplied new label shall be added to the * client_label_pending list, then saved at transaction-commit time to ensure * transaction-awareness. */ static void sepgsql_set_client_label(const char *new_label) { const char *tcontext; MemoryContext oldcxt; pending_label *plabel; /* Reset to the initial client label, if NULL */ if (!new_label) tcontext = client_label_peer; else { if (security_check_context_raw((security_context_t) new_label) < 0) ereport(ERROR, (errcode(ERRCODE_INVALID_NAME), errmsg("SELinux: invalid security label: \"%s\"", new_label))); tcontext = new_label; } /* Check process:{setcurrent} permission. */ sepgsql_avc_check_perms_label(sepgsql_get_client_label(), SEPG_CLASS_PROCESS, SEPG_PROCESS__SETCURRENT, NULL, true); /* Check process:{dyntransition} permission. */ sepgsql_avc_check_perms_label(tcontext, SEPG_CLASS_PROCESS, SEPG_PROCESS__DYNTRANSITION, NULL, true); /* * Append the supplied new_label on the pending list until the current * transaction is committed. */ oldcxt = MemoryContextSwitchTo(CurTransactionContext); plabel = palloc0(sizeof(pending_label)); plabel->subid = GetCurrentSubTransactionId(); if (new_label) plabel->label = pstrdup(new_label); client_label_pending = lappend(client_label_pending, plabel); MemoryContextSwitchTo(oldcxt); } /* * sepgsql_xact_callback * * A callback routine of transaction commit/abort/prepare. Commit or abort * changes in the client_label_pending list. */ static void sepgsql_xact_callback(XactEvent event, void *arg) { if (event == XACT_EVENT_COMMIT) { if (client_label_pending != NIL) { pending_label *plabel = llast(client_label_pending); char *new_label; if (plabel->label) new_label = MemoryContextStrdup(TopMemoryContext, plabel->label); else new_label = NULL; if (client_label_committed) pfree(client_label_committed); client_label_committed = new_label; /* * XXX - Note that items of client_label_pending are allocated on * CurTransactionContext, thus, all acquired memory region shall * be released implicitly. */ client_label_pending = NIL; } } else if (event == XACT_EVENT_ABORT) client_label_pending = NIL; } /* * sepgsql_subxact_callback * * A callback routine of sub-transaction start/abort/commit. Releases all * security labels that are set within the sub-transaction that is aborted. */ static void sepgsql_subxact_callback(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg) { ListCell *cell; if (event == SUBXACT_EVENT_ABORT_SUB) { foreach(cell, client_label_pending) { pending_label *plabel = lfirst(cell); if (plabel->subid == mySubid) client_label_pending = foreach_delete_current(client_label_pending, cell); } } } /* * sepgsql_client_auth * * Entrypoint of the client authentication hook. * It switches the client label according to getpeercon(), and the current * performing mode according to the GUC setting. */ static void sepgsql_client_auth(Port *port, int status) { if (next_client_auth_hook) (*next_client_auth_hook) (port, status); /* * In the case when authentication failed, the supplied socket shall be * closed soon, so we don't need to do anything here. */ if (status != STATUS_OK) return; /* * Getting security label of the peer process using API of libselinux. */ if (getpeercon_raw(port->sock, &client_label_peer) < 0) ereport(FATAL, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: unable to get peer label: %m"))); /* * Switch the current performing mode from INTERNAL to either DEFAULT or * PERMISSIVE. */ if (sepgsql_get_permissive()) sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE); else sepgsql_set_mode(SEPGSQL_MODE_DEFAULT); } /* * sepgsql_needs_fmgr_hook * * It informs the core whether the supplied function is trusted procedure, * or not. If true, sepgsql_fmgr_hook shall be invoked at start, end, and * abort time of function invocation. */ static bool sepgsql_needs_fmgr_hook(Oid functionId) { ObjectAddress object; if (next_needs_fmgr_hook && (*next_needs_fmgr_hook) (functionId)) return true; /* * SELinux needs the function to be called via security_definer wrapper, * if this invocation will take a domain-transition. We call these * functions as trusted-procedure, if the security policy has a rule that * switches security label of the client on execution. */ if (sepgsql_avc_trusted_proc(functionId) != NULL) return true; /* * Even if not a trusted-procedure, this function should not be inlined * unless the client has db_procedure:{execute} permission. Please note * that it shall be actually failed later because of same reason with * ACL_EXECUTE. */ object.classId = ProcedureRelationId; object.objectId = functionId; object.objectSubId = 0; if (!sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_PROCEDURE, SEPG_DB_PROCEDURE__EXECUTE | SEPG_DB_PROCEDURE__ENTRYPOINT, SEPGSQL_AVC_NOAUDIT, false)) return true; return false; } /* * sepgsql_fmgr_hook * * It switches security label of the client on execution of trusted * procedures. */ static void sepgsql_fmgr_hook(FmgrHookEventType event, FmgrInfo *flinfo, Datum *private) { struct { char *old_label; char *new_label; Datum next_private; } *stack; switch (event) { case FHET_START: stack = (void *) DatumGetPointer(*private); if (!stack) { MemoryContext oldcxt; oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt); stack = palloc(sizeof(*stack)); stack->old_label = NULL; stack->new_label = sepgsql_avc_trusted_proc(flinfo->fn_oid); stack->next_private = 0; MemoryContextSwitchTo(oldcxt); /* * process:transition permission between old and new label, * when user tries to switch security label of the client on * execution of trusted procedure. * * Also, db_procedure:entrypoint permission should be checked * whether this procedure can perform as an entrypoint of the * trusted procedure, or not. Note that db_procedure:execute * permission shall be checked individually. */ if (stack->new_label) { ObjectAddress object; object.classId = ProcedureRelationId; object.objectId = flinfo->fn_oid; object.objectSubId = 0; sepgsql_avc_check_perms(&object, SEPG_CLASS_DB_PROCEDURE, SEPG_DB_PROCEDURE__ENTRYPOINT, getObjectDescription(&object), true); sepgsql_avc_check_perms_label(stack->new_label, SEPG_CLASS_PROCESS, SEPG_PROCESS__TRANSITION, NULL, true); } *private = PointerGetDatum(stack); } Assert(!stack->old_label); if (stack->new_label) { stack->old_label = client_label_func; client_label_func = stack->new_label; } if (next_fmgr_hook) (*next_fmgr_hook) (event, flinfo, &stack->next_private); break; case FHET_END: case FHET_ABORT: stack = (void *) DatumGetPointer(*private); if (next_fmgr_hook) (*next_fmgr_hook) (event, flinfo, &stack->next_private); if (stack->new_label) { client_label_func = stack->old_label; stack->old_label = NULL; } break; default: elog(ERROR, "unexpected event type: %d", (int) event); break; } } /* * sepgsql_init_client_label * * Initializes the client security label and sets up related hooks for client * label management. */ void sepgsql_init_client_label(void) { /* * Set up dummy client label. * * XXX - note that PostgreSQL launches background worker process like * autovacuum without authentication steps. So, we initialize sepgsql_mode * with SEPGSQL_MODE_INTERNAL, and client_label with the security context * of server process. Later, it also launches background of user session. * In this case, the process is always hooked on post-authentication, and * we can initialize the sepgsql_mode and client_label correctly. */ if (getcon_raw(&client_label_peer) < 0) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: failed to get server security label: %m"))); /* Client authentication hook */ next_client_auth_hook = ClientAuthentication_hook; ClientAuthentication_hook = sepgsql_client_auth; /* Trusted procedure hooks */ next_needs_fmgr_hook = needs_fmgr_hook; needs_fmgr_hook = sepgsql_needs_fmgr_hook; next_fmgr_hook = fmgr_hook; fmgr_hook = sepgsql_fmgr_hook; /* Transaction/Sub-transaction callbacks */ RegisterXactCallback(sepgsql_xact_callback, NULL); RegisterSubXactCallback(sepgsql_subxact_callback, NULL); } /* * 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: failed to get initial security label: %m"))); 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("SELinux: invalid security label: \"%s\"", seclabel))); /* * Do actual permission checks for each object classes */ switch (object->classId) { case DatabaseRelationId: sepgsql_database_relabel(object->objectId, seclabel); break; 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: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("sepgsql provider does not support labels on %s", getObjectTypeDescription(object)))); 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()) PG_RETURN_NULL(); client_label = sepgsql_get_client_label(); PG_RETURN_TEXT_P(cstring_to_text(client_label)); } /* * BOOL sepgsql_setcon(TEXT) * * It switches the security label of the client. */ PG_FUNCTION_INFO_V1(sepgsql_setcon); Datum sepgsql_setcon(PG_FUNCTION_ARGS) { const char *new_label; if (PG_ARGISNULL(0)) new_label = NULL; else new_label = TextDatumGetCString(PG_GETARG_DATUM(0)); sepgsql_set_client_label(new_label); PG_RETURN_BOOL(true); } /* * 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_PP(0); char *raw_label; char *result; if (!sepgsql_is_enabled()) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("sepgsql is not enabled"))); if (selinux_trans_to_raw_context(text_to_cstring(label), &raw_label) < 0) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: could not translate security label: %m"))); PG_TRY(); { result = pstrdup(raw_label); } PG_CATCH(); { freecon(raw_label); PG_RE_THROW(); } PG_END_TRY(); freecon(raw_label); PG_RETURN_TEXT_P(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_PP(0); char *qual_label; char *result; if (!sepgsql_is_enabled()) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("sepgsql is not currently enabled"))); if (selinux_raw_to_trans_context(text_to_cstring(label), &qual_label) < 0) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: could not translate security label: %m"))); PG_TRY(); { result = pstrdup(qual_label); } PG_CATCH(); { freecon(qual_label); PG_RE_THROW(); } PG_END_TRY(); freecon(qual_label); PG_RETURN_TEXT_P(cstring_to_text(result)); } /* * quote_object_name * * It tries to quote the supplied identifiers */ static char * quote_object_name(const char *src1, const char *src2, const char *src3, const char *src4) { StringInfoData result; const char *temp; initStringInfo(&result); if (src1) { temp = quote_identifier(src1); appendStringInfoString(&result, temp); if (src1 != temp) pfree((void *) temp); } if (src2) { temp = quote_identifier(src2); appendStringInfo(&result, ".%s", temp); if (src2 != temp) pfree((void *) temp); } if (src3) { temp = quote_identifier(src3); appendStringInfo(&result, ".%s", temp); if (src3 != temp) pfree((void *) temp); } if (src4) { temp = quote_identifier(src4); appendStringInfo(&result, ".%s", temp); if (src4 != temp) pfree((void *) temp); } return result.data; } /* * 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 = table_open(catalogId, AccessShareLock); sscan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL); while (HeapTupleIsValid(tuple = systable_getnext(sscan))) { Form_pg_database datForm; Form_pg_namespace nspForm; Form_pg_class relForm; Form_pg_attribute attForm; Form_pg_proc proForm; char *objname; 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 DatabaseRelationId: datForm = (Form_pg_database) GETSTRUCT(tuple); objtype = SELABEL_DB_DATABASE; objname = quote_object_name(NameStr(datForm->datname), NULL, NULL, NULL); object.classId = DatabaseRelationId; object.objectId = datForm->oid; object.objectSubId = 0; break; case NamespaceRelationId: nspForm = (Form_pg_namespace) GETSTRUCT(tuple); objtype = SELABEL_DB_SCHEMA; objname = quote_object_name(database_name, NameStr(nspForm->nspname), NULL, NULL); object.classId = NamespaceRelationId; object.objectId = nspForm->oid; object.objectSubId = 0; break; case RelationRelationId: relForm = (Form_pg_class) GETSTRUCT(tuple); if (relForm->relkind == RELKIND_RELATION || relForm->relkind == RELKIND_PARTITIONED_TABLE) 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); objname = quote_object_name(database_name, namespace_name, NameStr(relForm->relname), NULL); pfree(namespace_name); object.classId = RelationRelationId; object.objectId = relForm->oid; object.objectSubId = 0; break; case AttributeRelationId: attForm = (Form_pg_attribute) GETSTRUCT(tuple); if (get_rel_relkind(attForm->attrelid) != RELKIND_RELATION && get_rel_relkind(attForm->attrelid) != RELKIND_PARTITIONED_TABLE) 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); objname = quote_object_name(database_name, namespace_name, relation_name, NameStr(attForm->attname)); pfree(namespace_name); pfree(relation_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); objname = quote_object_name(database_name, namespace_name, NameStr(proForm->proname), NULL); pfree(namespace_name); object.classId = ProcedureRelationId; object.objectId = proForm->oid; object.objectSubId = 0; break; default: elog(ERROR, "unexpected catalog id: %u", catalogId); objname = NULL; /* for compiler quiet */ 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("SELinux: no initial label assigned for %s (type=%d), skipping", objname, objtype))); else ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: could not determine initial security label for %s (type=%d): %m", objname, objtype))); pfree(objname); } systable_endscan(sscan); table_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_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("sepgsql is not currently enabled"))); /* * 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("SELinux: 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: failed to initialize labeling handle: %m"))); PG_TRY(); { exec_object_restorecon(sehnd, DatabaseRelationId); 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); }