| /* ------------------------------------------------------------------------- |
| * |
| * contrib/sepgsql/hooks.c |
| * |
| * Entrypoints of the hooks in PostgreSQL, and dispatches the callbacks. |
| * |
| * Copyright (c) 2010-2021, 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 |
| */ |
| void _PG_init(void); |
| |
| /* |
| * 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; |
| |
| bool |
| sepgsql_get_permissive(void) |
| { |
| return sepgsql_permissive; |
| } |
| |
| /* |
| * GUC: sepgsql.debug_audit = (on|off) |
| */ |
| static bool sepgsql_debug_audit; |
| |
| 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 *rangeTabls, 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) (rangeTabls, abort)) |
| return false; |
| |
| if (!sepgsql_dml_privileges(rangeTabls, 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); |
| |
| /* 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)); |
| } |