postgresql/contrib/sepgsql/hooks.c
Tom Lane 2594cf0e8c Revise the API for GUC variable assign hooks.
The previous functions of assign hooks are now split between check hooks
and assign hooks, where the former can fail but the latter shouldn't.
Aside from being conceptually clearer, this approach exposes the
"canonicalized" form of the variable value to guc.c without having to do
an actual assignment.  And that lets us fix the problem recently noted by
Bernd Helmle that the auto-tune patch for wal_buffers resulted in bogus
log messages about "parameter "wal_buffers" cannot be changed without
restarting the server".  There may be some speed advantage too, because
this design lets hook functions avoid re-parsing variable values when
restoring a previous state after a rollback (they can store a pre-parsed
representation of the value instead).  This patch also resolves a
longstanding annoyance about custom error messages from variable assign
hooks: they should modify, not appear separately from, guc.c's own message
about "invalid parameter value".
2011-04-07 00:12:02 -04:00

463 lines
11 KiB
C

/* -------------------------------------------------------------------------
*
* 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;
}