mirror of
https://github.com/postgres/postgres.git
synced 2025-06-13 07:41:39 +03:00
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
This commit is contained in:
446
contrib/sepgsql/hooks.c
Normal file
446
contrib/sepgsql/hooks.c
Normal file
@ -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;
|
||||
}
|
Reference in New Issue
Block a user