aboutsummaryrefslogtreecommitdiff
path: root/contrib/sepgsql/hooks.c
diff options
context:
space:
mode:
authorRobert Haas <rhaas@postgresql.org>2011-01-23 20:44:48 -0500
committerRobert Haas <rhaas@postgresql.org>2011-01-23 20:48:27 -0500
commit968bc6fac91d6aaca594488ab85c179b686cbbdd (patch)
tree3cb8fa7ee4101723733e5ed5a06803f9c299c2d7 /contrib/sepgsql/hooks.c
parente5487f65fdbd05716ade642a3ae1c5c6e85b6f22 (diff)
downloadpostgresql-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.c446
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;
+}