diff options
author | Robert Haas <rhaas@postgresql.org> | 2011-01-23 20:44:48 -0500 |
---|---|---|
committer | Robert Haas <rhaas@postgresql.org> | 2011-01-23 20:48:27 -0500 |
commit | 968bc6fac91d6aaca594488ab85c179b686cbbdd (patch) | |
tree | 3cb8fa7ee4101723733e5ed5a06803f9c299c2d7 /contrib/sepgsql/hooks.c | |
parent | e5487f65fdbd05716ade642a3ae1c5c6e85b6f22 (diff) | |
download | postgresql-968bc6fac91d6aaca594488ab85c179b686cbbdd.tar.gz postgresql-968bc6fac91d6aaca594488ab85c179b686cbbdd.zip |
sepgsql, an SE-Linux integration for PostgreSQL
This is still pretty rough - among other things, the documentation
needs work, and the messages need a visit from the style police -
but this gets the basic framework in place.
KaiGai Kohei
Diffstat (limited to 'contrib/sepgsql/hooks.c')
-rw-r--r-- | contrib/sepgsql/hooks.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c new file mode 100644 index 00000000000..6b55e484cfd --- /dev/null +++ b/contrib/sepgsql/hooks.c @@ -0,0 +1,446 @@ +/* ------------------------------------------------------------------------- + * + * 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: failed to get the peer label"))); + + 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; + + 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); + + *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 allowed anyway."))); + } + 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_FEATURE_NOT_SUPPORTED), + errmsg("Not allowed to load SE-PostgreSQL now"))); + + /* + * 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); + + /* + * 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); + + /* + * 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: unable to get security label of server"))); + 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; +} |