mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			463 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			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;
 | 
						|
}
 |