/* ------------------------------------------------------------------------- * * contrib/sepgsql/hooks.c * * Entrypoints of the hooks in PostgreSQL, and dispatches the callbacks. * * Copyright (c) 2010-2011, PostgreSQL Global Development Group * * ------------------------------------------------------------------------- */ #include "postgres.h" #include "catalog/objectaccess.h" #include "catalog/pg_class.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "commands/seclabel.h" #include "executor/executor.h" #include "fmgr.h" #include "libpq/auth.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 ClientAuthentication_hook_type next_client_auth_hook = NULL; static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL; static needs_fmgr_hook_type next_needs_fmgr_hook = NULL; static fmgr_hook_type next_fmgr_hook = NULL; static ProcessUtility_hook_type next_ProcessUtility_hook = NULL; /* * 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_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) { char *context; 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, &context) < 0) ereport(FATAL, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: unable to get peer label: %m"))); sepgsql_set_client_label(context); /* * Switch the current performing mode from INTERNAL to either DEFAULT or * PERMISSIVE. */ if (sepgsql_permissive) sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE); else sepgsql_set_mode(SEPGSQL_MODE_DEFAULT); } /* * 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 NamespaceRelationId: sepgsql_schema_post_create(objectId); break; case RelationRelationId: if (subId == 0) sepgsql_relation_post_create(objectId); 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_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) { char *old_label; char *new_label; char *function_label; 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. */ old_label = sepgsql_get_client_label(); new_label = sepgsql_proc_get_domtrans(functionId); if (strcmp(old_label, new_label) != 0) { pfree(new_label); return true; } pfree(new_label); /* * 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. */ function_label = sepgsql_get_label(ProcedureRelationId, functionId, 0); if (sepgsql_check_perms(sepgsql_get_client_label(), function_label, SEPG_CLASS_DB_PROCEDURE, SEPG_DB_PROCEDURE__EXECUTE, NULL, false) != true) { pfree(function_label); return true; } pfree(function_label); 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; const char *cur_label = sepgsql_get_client_label(); oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt); stack = palloc(sizeof(*stack)); stack->old_label = NULL; stack->new_label = sepgsql_proc_get_domtrans(flinfo->fn_oid); stack->next_private = 0; MemoryContextSwitchTo(oldcxt); if (strcmp(cur_label, stack->new_label) != 0) { /* * process:transition permission between old and new * label, when user tries to switch security label of the * client on execution of trusted procedure. */ sepgsql_check_perms(cur_label, stack->new_label, SEPG_CLASS_PROCESS, SEPG_PROCESS__TRANSITION, NULL, true); } *private = PointerGetDatum(stack); } Assert(!stack->old_label); stack->old_label = sepgsql_set_client_label(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); sepgsql_set_client_label(stack->old_label); stack->old_label = NULL; break; default: elog(ERROR, "unexpected event type: %d", (int) event); break; } } /* * 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) { if (next_ProcessUtility_hook) (*next_ProcessUtility_hook) (parsetree, queryString, params, isTopLevel, dest, completionTag); /* * Check command tag to avoid nefarious operations */ switch (nodeTag(parsetree)) { 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; } /* * Original implementation */ standard_ProcessUtility(parsetree, queryString, params, isTopLevel, dest, completionTag); } /* * Module load/unload callback */ void _PG_init(void) { char *context; /* * 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); /* * 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(&context) < 0) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: failed to get server security label: %m"))); sepgsql_set_client_label(context); /* Security label provider hook */ register_label_provider(SEPGSQL_LABEL_TAG, sepgsql_object_relabel); /* Client authentication hook */ next_client_auth_hook = ClientAuthentication_hook; ClientAuthentication_hook = sepgsql_client_auth; /* 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; /* 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; /* ProcessUtility hook */ next_ProcessUtility_hook = ProcessUtility_hook; ProcessUtility_hook = sepgsql_utility_command; }