mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	Use SELinux "db_table: { truncate }" to check if permission is granted to
TRUNCATE. Update example SELinux policy to grant needed permission for
TRUNCATE. Add new regression test to demonstrate a positive and negative
cases. Test will only be run if the loaded SELinux policy has the
"db_table: { truncate }" permission. Makes use of recent commit which added
object TRUNCATE hook. Patch by Yuli Khodorkovskiy with minor
editorialization by me. Not back-patched because the object TRUNCATE hook
was not.
Author: Yuli Khodorkovskiy
Reviewed-by: Joe Conway
Discussion: https://postgr.es/m/CAFL5wJcomybj1Xdw7qWmPJRpGuFukKgNrDb6uVBaCMgYS9dkaA%40mail.gmail.com
		
	
		
			
				
	
	
		
			482 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			482 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* -------------------------------------------------------------------------
 | 
						|
 *
 | 
						|
 * contrib/sepgsql/hooks.c
 | 
						|
 *
 | 
						|
 * Entrypoints of the hooks in PostgreSQL, and dispatches the callbacks.
 | 
						|
 *
 | 
						|
 * Copyright (c) 2010-2019, PostgreSQL Global Development Group
 | 
						|
 *
 | 
						|
 * -------------------------------------------------------------------------
 | 
						|
 */
 | 
						|
#include "postgres.h"
 | 
						|
 | 
						|
#include "catalog/dependency.h"
 | 
						|
#include "catalog/objectaccess.h"
 | 
						|
#include "catalog/pg_class.h"
 | 
						|
#include "catalog/pg_database.h"
 | 
						|
#include "catalog/pg_namespace.h"
 | 
						|
#include "catalog/pg_proc.h"
 | 
						|
#include "commands/seclabel.h"
 | 
						|
#include "executor/executor.h"
 | 
						|
#include "fmgr.h"
 | 
						|
#include "miscadmin.h"
 | 
						|
#include "sepgsql.h"
 | 
						|
#include "tcop/utility.h"
 | 
						|
#include "utils/guc.h"
 | 
						|
#include "utils/queryenvironment.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 ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
 | 
						|
static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 | 
						|
 | 
						|
/*
 | 
						|
 * Contextual information on DDL commands
 | 
						|
 */
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
	NodeTag		cmdtype;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Name of the template database given by users on CREATE DATABASE
 | 
						|
	 * command. Elsewhere (including the case of default) NULL.
 | 
						|
	 */
 | 
						|
	const char *createdb_dtemplate;
 | 
						|
}			sepgsql_context_info_t;
 | 
						|
 | 
						|
static sepgsql_context_info_t sepgsql_context_info;
 | 
						|
 | 
						|
/*
 | 
						|
 * 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_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,
 | 
						|
					  void *arg)
 | 
						|
{
 | 
						|
	if (next_object_access_hook)
 | 
						|
		(*next_object_access_hook) (access, classId, objectId, subId, arg);
 | 
						|
 | 
						|
	switch (access)
 | 
						|
	{
 | 
						|
		case OAT_POST_CREATE:
 | 
						|
			{
 | 
						|
				ObjectAccessPostCreate *pc_arg = arg;
 | 
						|
				bool		is_internal;
 | 
						|
 | 
						|
				is_internal = pc_arg ? pc_arg->is_internal : false;
 | 
						|
 | 
						|
				switch (classId)
 | 
						|
				{
 | 
						|
					case DatabaseRelationId:
 | 
						|
						Assert(!is_internal);
 | 
						|
						sepgsql_database_post_create(objectId,
 | 
						|
													 sepgsql_context_info.createdb_dtemplate);
 | 
						|
						break;
 | 
						|
 | 
						|
					case NamespaceRelationId:
 | 
						|
						Assert(!is_internal);
 | 
						|
						sepgsql_schema_post_create(objectId);
 | 
						|
						break;
 | 
						|
 | 
						|
					case RelationRelationId:
 | 
						|
						if (subId == 0)
 | 
						|
						{
 | 
						|
							/*
 | 
						|
							 * The cases in which we want to apply permission
 | 
						|
							 * checks on creation of a new relation correspond
 | 
						|
							 * to direct user invocation.  For internal uses,
 | 
						|
							 * that is creation of toast tables, index rebuild
 | 
						|
							 * or ALTER TABLE commands, we need neither
 | 
						|
							 * assignment of security labels nor permission
 | 
						|
							 * checks.
 | 
						|
							 */
 | 
						|
							if (is_internal)
 | 
						|
								break;
 | 
						|
 | 
						|
							sepgsql_relation_post_create(objectId);
 | 
						|
						}
 | 
						|
						else
 | 
						|
							sepgsql_attribute_post_create(objectId, subId);
 | 
						|
						break;
 | 
						|
 | 
						|
					case ProcedureRelationId:
 | 
						|
						Assert(!is_internal);
 | 
						|
						sepgsql_proc_post_create(objectId);
 | 
						|
						break;
 | 
						|
 | 
						|
					default:
 | 
						|
						/* Ignore unsupported object classes */
 | 
						|
						break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			break;
 | 
						|
 | 
						|
		case OAT_DROP:
 | 
						|
			{
 | 
						|
				ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
 | 
						|
 | 
						|
				/*
 | 
						|
				 * No need to apply permission checks on object deletion due
 | 
						|
				 * to internal cleanups; such as removal of temporary database
 | 
						|
				 * object on session closed.
 | 
						|
				 */
 | 
						|
				if ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) != 0)
 | 
						|
					break;
 | 
						|
 | 
						|
				switch (classId)
 | 
						|
				{
 | 
						|
					case DatabaseRelationId:
 | 
						|
						sepgsql_database_drop(objectId);
 | 
						|
						break;
 | 
						|
 | 
						|
					case NamespaceRelationId:
 | 
						|
						sepgsql_schema_drop(objectId);
 | 
						|
						break;
 | 
						|
 | 
						|
					case RelationRelationId:
 | 
						|
						if (subId == 0)
 | 
						|
							sepgsql_relation_drop(objectId);
 | 
						|
						else
 | 
						|
							sepgsql_attribute_drop(objectId, subId);
 | 
						|
						break;
 | 
						|
 | 
						|
					case ProcedureRelationId:
 | 
						|
						sepgsql_proc_drop(objectId);
 | 
						|
						break;
 | 
						|
 | 
						|
					default:
 | 
						|
						/* Ignore unsupported object classes */
 | 
						|
						break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			break;
 | 
						|
 | 
						|
		case OAT_TRUNCATE:
 | 
						|
			{
 | 
						|
				switch (classId)
 | 
						|
				{
 | 
						|
					case RelationRelationId:
 | 
						|
						sepgsql_relation_truncate(objectId);
 | 
						|
						break;
 | 
						|
					default:
 | 
						|
						/* Ignore unsupported object classes */
 | 
						|
						break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			break;
 | 
						|
 | 
						|
		case OAT_POST_ALTER:
 | 
						|
			{
 | 
						|
				ObjectAccessPostAlter *pa_arg = arg;
 | 
						|
				bool		is_internal = pa_arg->is_internal;
 | 
						|
 | 
						|
				switch (classId)
 | 
						|
				{
 | 
						|
					case DatabaseRelationId:
 | 
						|
						Assert(!is_internal);
 | 
						|
						sepgsql_database_setattr(objectId);
 | 
						|
						break;
 | 
						|
 | 
						|
					case NamespaceRelationId:
 | 
						|
						Assert(!is_internal);
 | 
						|
						sepgsql_schema_setattr(objectId);
 | 
						|
						break;
 | 
						|
 | 
						|
					case RelationRelationId:
 | 
						|
						if (subId == 0)
 | 
						|
						{
 | 
						|
							/*
 | 
						|
							 * A case when we don't want to apply permission
 | 
						|
							 * check is that relation is internally altered
 | 
						|
							 * without user's intention. E.g, no need to check
 | 
						|
							 * on toast table/index to be renamed at end of
 | 
						|
							 * the table rewrites.
 | 
						|
							 */
 | 
						|
							if (is_internal)
 | 
						|
								break;
 | 
						|
 | 
						|
							sepgsql_relation_setattr(objectId);
 | 
						|
						}
 | 
						|
						else
 | 
						|
							sepgsql_attribute_setattr(objectId, subId);
 | 
						|
						break;
 | 
						|
 | 
						|
					case ProcedureRelationId:
 | 
						|
						Assert(!is_internal);
 | 
						|
						sepgsql_proc_setattr(objectId);
 | 
						|
						break;
 | 
						|
 | 
						|
					default:
 | 
						|
						/* Ignore unsupported object classes */
 | 
						|
						break;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			break;
 | 
						|
 | 
						|
		case OAT_NAMESPACE_SEARCH:
 | 
						|
			{
 | 
						|
				ObjectAccessNamespaceSearch *ns_arg = arg;
 | 
						|
 | 
						|
				/*
 | 
						|
				 * If stacked extension already decided not to allow users to
 | 
						|
				 * search this schema, we just stick with that decision.
 | 
						|
				 */
 | 
						|
				if (!ns_arg->result)
 | 
						|
					break;
 | 
						|
 | 
						|
				Assert(classId == NamespaceRelationId);
 | 
						|
				Assert(ns_arg->result);
 | 
						|
				ns_arg->result
 | 
						|
					= sepgsql_schema_search(objectId,
 | 
						|
											ns_arg->ereport_on_violation);
 | 
						|
			}
 | 
						|
			break;
 | 
						|
 | 
						|
		case OAT_FUNCTION_EXECUTE:
 | 
						|
			{
 | 
						|
				Assert(classId == ProcedureRelationId);
 | 
						|
				sepgsql_proc_execute(objectId);
 | 
						|
			}
 | 
						|
			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_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(PlannedStmt *pstmt,
 | 
						|
						const char *queryString,
 | 
						|
						ProcessUtilityContext context,
 | 
						|
						ParamListInfo params,
 | 
						|
						QueryEnvironment *queryEnv,
 | 
						|
						DestReceiver *dest,
 | 
						|
						char *completionTag)
 | 
						|
{
 | 
						|
	Node	   *parsetree = pstmt->utilityStmt;
 | 
						|
	sepgsql_context_info_t saved_context_info = sepgsql_context_info;
 | 
						|
	ListCell   *cell;
 | 
						|
 | 
						|
	PG_TRY();
 | 
						|
	{
 | 
						|
		/*
 | 
						|
		 * Check command tag to avoid nefarious operations, and save the
 | 
						|
		 * current contextual information to determine whether we should apply
 | 
						|
		 * permission checks here, or not.
 | 
						|
		 */
 | 
						|
		sepgsql_context_info.cmdtype = nodeTag(parsetree);
 | 
						|
 | 
						|
		switch (nodeTag(parsetree))
 | 
						|
		{
 | 
						|
			case T_CreatedbStmt:
 | 
						|
 | 
						|
				/*
 | 
						|
				 * We hope to reference name of the source database, but it
 | 
						|
				 * does not appear in system catalog. So, we save it here.
 | 
						|
				 */
 | 
						|
				foreach(cell, ((CreatedbStmt *) parsetree)->options)
 | 
						|
				{
 | 
						|
					DefElem    *defel = (DefElem *) lfirst(cell);
 | 
						|
 | 
						|
					if (strcmp(defel->defname, "template") == 0)
 | 
						|
					{
 | 
						|
						sepgsql_context_info.createdb_dtemplate
 | 
						|
							= strVal(defel->arg);
 | 
						|
						break;
 | 
						|
					}
 | 
						|
				}
 | 
						|
				break;
 | 
						|
 | 
						|
			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;
 | 
						|
		}
 | 
						|
 | 
						|
		if (next_ProcessUtility_hook)
 | 
						|
			(*next_ProcessUtility_hook) (pstmt, queryString,
 | 
						|
										 context, params, queryEnv,
 | 
						|
										 dest, completionTag);
 | 
						|
		else
 | 
						|
			standard_ProcessUtility(pstmt, queryString,
 | 
						|
									context, params, queryEnv,
 | 
						|
									dest, completionTag);
 | 
						|
	}
 | 
						|
	PG_FINALLY();
 | 
						|
	{
 | 
						|
		sepgsql_context_info = saved_context_info;
 | 
						|
	}
 | 
						|
	PG_END_TRY();
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Module load/unload callback
 | 
						|
 */
 | 
						|
void
 | 
						|
_PG_init(void)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * 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);
 | 
						|
 | 
						|
	/* Initialize userspace access vector cache */
 | 
						|
	sepgsql_avc_init();
 | 
						|
 | 
						|
	/* Initialize security label of the client and related stuff */
 | 
						|
	sepgsql_init_client_label();
 | 
						|
 | 
						|
	/* Security label provider hook */
 | 
						|
	register_label_provider(SEPGSQL_LABEL_TAG,
 | 
						|
							sepgsql_object_relabel);
 | 
						|
 | 
						|
	/* 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;
 | 
						|
 | 
						|
	/* ProcessUtility hook */
 | 
						|
	next_ProcessUtility_hook = ProcessUtility_hook;
 | 
						|
	ProcessUtility_hook = sepgsql_utility_command;
 | 
						|
 | 
						|
	/* init contextual info */
 | 
						|
	memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info));
 | 
						|
}
 |