diff options
Diffstat (limited to 'contrib/sepgsql/hooks.c')
-rw-r--r-- | contrib/sepgsql/hooks.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c new file mode 100644 index 0000000..fa73476 --- /dev/null +++ b/contrib/sepgsql/hooks.c @@ -0,0 +1,483 @@ +/* ------------------------------------------------------------------------- + * + * contrib/sepgsql/hooks.c + * + * Entrypoints of the hooks in PostgreSQL, and dispatches the callbacks. + * + * Copyright (c) 2010-2023, PostgreSQL Global Development Group + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/dependency.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_class.h" +#include "catalog/pg_database.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_proc.h" +#include "commands/seclabel.h" +#include "executor/executor.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "sepgsql.h" +#include "tcop/utility.h" +#include "utils/guc.h" +#include "utils/queryenvironment.h" + +PG_MODULE_MAGIC; + +/* + * Declarations + */ + +/* + * Saved hook entries (if stacked) + */ +static object_access_hook_type next_object_access_hook = NULL; +static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL; +static ProcessUtility_hook_type next_ProcessUtility_hook = NULL; + +/* + * Contextual information on DDL commands + */ +typedef struct +{ + NodeTag cmdtype; + + /* + * Name of the template database given by users on CREATE DATABASE + * command. Elsewhere (including the case of default) NULL. + */ + const char *createdb_dtemplate; +} sepgsql_context_info_t; + +static sepgsql_context_info_t sepgsql_context_info; + +/* + * GUC: sepgsql.permissive = (on|off) + */ +static bool sepgsql_permissive = false; + +bool +sepgsql_get_permissive(void) +{ + return sepgsql_permissive; +} + +/* + * GUC: sepgsql.debug_audit = (on|off) + */ +static bool sepgsql_debug_audit = false; + +bool +sepgsql_get_debug_audit(void) +{ + return sepgsql_debug_audit; +} + +/* + * sepgsql_object_access + * + * Entrypoint of the object_access_hook. This routine performs as + * a dispatcher of invocation based on access type and object classes. + */ +static void +sepgsql_object_access(ObjectAccessType access, + Oid classId, + Oid objectId, + int subId, + void *arg) +{ + if (next_object_access_hook) + (*next_object_access_hook) (access, classId, objectId, subId, arg); + + switch (access) + { + case OAT_POST_CREATE: + { + ObjectAccessPostCreate *pc_arg = arg; + bool is_internal; + + is_internal = pc_arg ? pc_arg->is_internal : false; + + switch (classId) + { + case DatabaseRelationId: + Assert(!is_internal); + sepgsql_database_post_create(objectId, + sepgsql_context_info.createdb_dtemplate); + break; + + case NamespaceRelationId: + Assert(!is_internal); + sepgsql_schema_post_create(objectId); + break; + + case RelationRelationId: + if (subId == 0) + { + /* + * The cases in which we want to apply permission + * checks on creation of a new relation correspond + * to direct user invocation. For internal uses, + * that is creation of toast tables, index rebuild + * or ALTER TABLE commands, we need neither + * assignment of security labels nor permission + * checks. + */ + if (is_internal) + break; + + sepgsql_relation_post_create(objectId); + } + else + sepgsql_attribute_post_create(objectId, subId); + break; + + case ProcedureRelationId: + Assert(!is_internal); + sepgsql_proc_post_create(objectId); + break; + + default: + /* Ignore unsupported object classes */ + break; + } + } + break; + + case OAT_DROP: + { + ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg; + + /* + * No need to apply permission checks on object deletion due + * to internal cleanups; such as removal of temporary database + * object on session closed. + */ + if ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) != 0) + break; + + switch (classId) + { + case DatabaseRelationId: + sepgsql_database_drop(objectId); + break; + + case NamespaceRelationId: + sepgsql_schema_drop(objectId); + break; + + case RelationRelationId: + if (subId == 0) + sepgsql_relation_drop(objectId); + else + sepgsql_attribute_drop(objectId, subId); + break; + + case ProcedureRelationId: + sepgsql_proc_drop(objectId); + break; + + default: + /* Ignore unsupported object classes */ + break; + } + } + break; + + case OAT_TRUNCATE: + { + switch (classId) + { + case RelationRelationId: + sepgsql_relation_truncate(objectId); + break; + default: + /* Ignore unsupported object classes */ + break; + } + } + break; + + case OAT_POST_ALTER: + { + ObjectAccessPostAlter *pa_arg = arg; + bool is_internal = pa_arg->is_internal; + + switch (classId) + { + case DatabaseRelationId: + Assert(!is_internal); + sepgsql_database_setattr(objectId); + break; + + case NamespaceRelationId: + Assert(!is_internal); + sepgsql_schema_setattr(objectId); + break; + + case RelationRelationId: + if (subId == 0) + { + /* + * A case when we don't want to apply permission + * check is that relation is internally altered + * without user's intention. E.g, no need to check + * on toast table/index to be renamed at end of + * the table rewrites. + */ + if (is_internal) + break; + + sepgsql_relation_setattr(objectId); + } + else + sepgsql_attribute_setattr(objectId, subId); + break; + + case ProcedureRelationId: + Assert(!is_internal); + sepgsql_proc_setattr(objectId); + break; + + default: + /* Ignore unsupported object classes */ + break; + } + } + break; + + case OAT_NAMESPACE_SEARCH: + { + ObjectAccessNamespaceSearch *ns_arg = arg; + + /* + * If stacked extension already decided not to allow users to + * search this schema, we just stick with that decision. + */ + if (!ns_arg->result) + break; + + Assert(classId == NamespaceRelationId); + Assert(ns_arg->result); + ns_arg->result + = sepgsql_schema_search(objectId, + ns_arg->ereport_on_violation); + } + break; + + case OAT_FUNCTION_EXECUTE: + { + Assert(classId == ProcedureRelationId); + sepgsql_proc_execute(objectId); + } + break; + + default: + elog(ERROR, "unexpected object access type: %d", (int) access); + break; + } +} + +/* + * sepgsql_exec_check_perms + * + * Entrypoint of DML permissions + */ +static bool +sepgsql_exec_check_perms(List *rangeTbls, List *rteperminfos, bool abort) +{ + /* + * If security provider is stacking and one of them replied 'false' at + * least, we don't need to check any more. + */ + if (next_exec_check_perms_hook && + !(*next_exec_check_perms_hook) (rangeTbls, rteperminfos, abort)) + return false; + + if (!sepgsql_dml_privileges(rangeTbls, rteperminfos, abort)) + return false; + + return true; +} + +/* + * sepgsql_utility_command + * + * It tries to rough-grained control on utility commands; some of them can + * break whole of the things if nefarious user would use. + */ +static void +sepgsql_utility_command(PlannedStmt *pstmt, + const char *queryString, + bool readOnlyTree, + ProcessUtilityContext context, + ParamListInfo params, + QueryEnvironment *queryEnv, + DestReceiver *dest, + QueryCompletion *qc) +{ + Node *parsetree = pstmt->utilityStmt; + sepgsql_context_info_t saved_context_info = sepgsql_context_info; + ListCell *cell; + + PG_TRY(); + { + /* + * Check command tag to avoid nefarious operations, and save the + * current contextual information to determine whether we should apply + * permission checks here, or not. + */ + sepgsql_context_info.cmdtype = nodeTag(parsetree); + + switch (nodeTag(parsetree)) + { + case T_CreatedbStmt: + + /* + * We hope to reference name of the source database, but it + * does not appear in system catalog. So, we save it here. + */ + foreach(cell, ((CreatedbStmt *) parsetree)->options) + { + DefElem *defel = (DefElem *) lfirst(cell); + + if (strcmp(defel->defname, "template") == 0) + { + sepgsql_context_info.createdb_dtemplate + = strVal(defel->arg); + break; + } + } + break; + + case T_LoadStmt: + + /* + * We reject LOAD command across the board on enforcing mode, + * because a binary module can arbitrarily override hooks. + */ + if (sepgsql_getenforce()) + { + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: LOAD is not permitted"))); + } + break; + default: + + /* + * Right now we don't check any other utility commands, + * because it needs more detailed information to make access + * control decision here, but we don't want to have two parse + * and analyze routines individually. + */ + break; + } + + if (next_ProcessUtility_hook) + (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + else + standard_ProcessUtility(pstmt, queryString, readOnlyTree, + context, params, queryEnv, + dest, qc); + } + PG_FINALLY(); + { + sepgsql_context_info = saved_context_info; + } + PG_END_TRY(); +} + +/* + * Module load/unload callback + */ +void +_PG_init(void) +{ + /* + * We allow to load the SE-PostgreSQL module on single-user-mode or + * shared_preload_libraries settings only. + */ + if (IsUnderPostmaster) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("sepgsql must be loaded via shared_preload_libraries"))); + + /* + * Check availability of SELinux on the platform. If disabled, we cannot + * activate any SE-PostgreSQL features, and we have to skip rest of + * initialization. + */ + if (is_selinux_enabled() < 1) + { + sepgsql_set_mode(SEPGSQL_MODE_DISABLED); + return; + } + + /* + * sepgsql.permissive = (on|off) + * + * This variable controls performing mode of SE-PostgreSQL on user's + * session. + */ + DefineCustomBoolVariable("sepgsql.permissive", + "Turn on/off permissive mode in SE-PostgreSQL", + NULL, + &sepgsql_permissive, + false, + PGC_SIGHUP, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + /* + * sepgsql.debug_audit = (on|off) + * + * This variable allows users to turn on/off audit logs on access control + * decisions, independent from auditallow/auditdeny setting in the + * security policy. We intend to use this option for debugging purpose. + */ + DefineCustomBoolVariable("sepgsql.debug_audit", + "Turn on/off debug audit messages", + NULL, + &sepgsql_debug_audit, + false, + PGC_USERSET, + GUC_NOT_IN_SAMPLE, + NULL, + NULL, + NULL); + + MarkGUCPrefixReserved("sepgsql"); + + /* Initialize userspace access vector cache */ + sepgsql_avc_init(); + + /* Initialize security label of the client and related stuff */ + sepgsql_init_client_label(); + + /* Security label provider hook */ + register_label_provider(SEPGSQL_LABEL_TAG, + sepgsql_object_relabel); + + /* Object access hook */ + next_object_access_hook = object_access_hook; + object_access_hook = sepgsql_object_access; + + /* DML permission check */ + next_exec_check_perms_hook = ExecutorCheckPerms_hook; + ExecutorCheckPerms_hook = sepgsql_exec_check_perms; + + /* ProcessUtility hook */ + next_ProcessUtility_hook = ProcessUtility_hook; + ProcessUtility_hook = sepgsql_utility_command; + + /* init contextual info */ + memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info)); +} |