/* ------------------------------------------------------------------------- * * contrib/sepgsql/hooks.c * * Entrypoints of the hooks in PostgreSQL, and dispatches the callbacks. * * Copyright (c) 2010-2012, PostgreSQL Global Development Group * * ------------------------------------------------------------------------- */ #include "postgres.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 "tcop/utility.h" #include "utils/guc.h" #include "sepgsql.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; static ExecutorStart_hook_type next_ExecutorStart_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) { if (next_object_access_hook) (*next_object_access_hook) (access, classId, objectId, subId); switch (access) { case OAT_POST_CREATE: switch (classId) { case DatabaseRelationId: sepgsql_database_post_create(objectId, sepgsql_context_info.createdb_dtemplate); break; case NamespaceRelationId: sepgsql_schema_post_create(objectId); break; case RelationRelationId: if (subId == 0) { /* * All cases we want to apply permission checks on * creation of a new relation are invocation of the * heap_create_with_catalog via DefineRelation or * OpenIntoRel. * Elsewhere, we need neither assignment of security * label nor permission checks. */ switch (sepgsql_context_info.cmdtype) { case T_CreateStmt: case T_ViewStmt: case T_CreateSeqStmt: case T_CompositeTypeStmt: case T_CreateForeignTableStmt: case T_SelectStmt: sepgsql_relation_post_create(objectId); break; default: /* via make_new_heap() */ break; } } else sepgsql_attribute_post_create(objectId, subId); break; case ProcedureRelationId: sepgsql_proc_post_create(objectId); break; default: /* Ignore unsupported object classes */ break; } 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_executor_start * * It saves contextual information during ExecutorStart to distinguish * a case with/without permission checks later. */ static void sepgsql_executor_start(QueryDesc *queryDesc, int eflags) { sepgsql_context_info_t saved_context_info = sepgsql_context_info; PG_TRY(); { if (queryDesc->operation == CMD_SELECT) sepgsql_context_info.cmdtype = T_SelectStmt; else if (queryDesc->operation == CMD_INSERT) sepgsql_context_info.cmdtype = T_InsertStmt; else if (queryDesc->operation == CMD_DELETE) sepgsql_context_info.cmdtype = T_DeleteStmt; else if (queryDesc->operation == CMD_UPDATE) sepgsql_context_info.cmdtype = T_UpdateStmt; /* * XXX - If queryDesc->operation is not above four cases, an error * shall be raised on the following executor stage soon. */ if (next_ExecutorStart_hook) (*next_ExecutorStart_hook) (queryDesc, eflags); else standard_ExecutorStart(queryDesc, eflags); } PG_CATCH(); { sepgsql_context_info = saved_context_info; PG_RE_THROW(); } PG_END_TRY(); sepgsql_context_info = saved_context_info; } /* * 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(Node *parsetree, const char *queryString, ParamListInfo params, bool isTopLevel, DestReceiver *dest, char *completionTag) { 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) (parsetree, queryString, params, isTopLevel, dest, completionTag); else standard_ProcessUtility(parsetree, queryString, params, isTopLevel, dest, completionTag); } PG_CATCH(); { sepgsql_context_info = saved_context_info; PG_RE_THROW(); } PG_END_TRY(); sepgsql_context_info = saved_context_info; } /* * 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; /* ExecutorStart hook */ next_ExecutorStart_hook = ExecutorStart_hook; ExecutorStart_hook = sepgsql_executor_start; /* init contextual info */ memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info)); }