mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-01-30 19:00:29 +08:00
a61b1f7482
Currently, information about the permissions to be checked on relations mentioned in a query is stored in their range table entries. So the executor must scan the entire range table looking for relations that need to have permissions checked. This can make the permission checking part of the executor initialization needlessly expensive when many inheritance children are present in the range range. While the permissions need not be checked on the individual child relations, the executor still must visit every range table entry to filter them out. This commit moves the permission checking information out of the range table entries into a new plan node called RTEPermissionInfo. Every top-level (inheritance "root") RTE_RELATION entry in the range table gets one and a list of those is maintained alongside the range table. This new list is initialized by the parser when initializing the range table. The rewriter can add more entries to it as rules/views are expanded. Finally, the planner combines the lists of the individual subqueries into one flat list that is passed to the executor for checking. To make it quick to find the RTEPermissionInfo entry belonging to a given relation, RangeTblEntry gets a new Index field 'perminfoindex' that stores the corresponding RTEPermissionInfo's index in the query's list of the latter. ExecutorCheckPerms_hook has gained another List * argument; the signature is now: typedef bool (*ExecutorCheckPerms_hook_type) (List *rangeTable, List *rtePermInfos, bool ereport_on_violation); The first argument is no longer used by any in-core uses of the hook, but we leave it in place because there may be other implementations that do. Implementations should likely scan the rtePermInfos list to determine which operations to allow or deny. Author: Amit Langote <amitlangote09@gmail.com> Discussion: https://postgr.es/m/CA+HiwqGjJDmUhDSfv-U2qhKJjt9ST7Xh9JXC_irsAQ1TAUsJYg@mail.gmail.com
484 lines
11 KiB
C
484 lines
11 KiB
C
/* -------------------------------------------------------------------------
|
|
*
|
|
* contrib/sepgsql/hooks.c
|
|
*
|
|
* Entrypoints of the hooks in PostgreSQL, and dispatches the callbacks.
|
|
*
|
|
* Copyright (c) 2010-2022, 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
|
|
*/
|
|
|
|
/*
|
|
* 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 = false;
|
|
|
|
bool
|
|
sepgsql_get_permissive(void)
|
|
{
|
|
return sepgsql_permissive;
|
|
}
|
|
|
|
/*
|
|
* GUC: sepgsql.debug_audit = (on|off)
|
|
*/
|
|
static bool sepgsql_debug_audit = false;
|
|
|
|
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 *rangeTbls, List *rteperminfos, 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) (rangeTbls, rteperminfos, abort))
|
|
return false;
|
|
|
|
if (!sepgsql_dml_privileges(rangeTbls, rteperminfos, 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,
|
|
bool readOnlyTree,
|
|
ProcessUtilityContext context,
|
|
ParamListInfo params,
|
|
QueryEnvironment *queryEnv,
|
|
DestReceiver *dest,
|
|
QueryCompletion *qc)
|
|
{
|
|
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, readOnlyTree,
|
|
context, params, queryEnv,
|
|
dest, qc);
|
|
else
|
|
standard_ProcessUtility(pstmt, queryString, readOnlyTree,
|
|
context, params, queryEnv,
|
|
dest, qc);
|
|
}
|
|
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);
|
|
|
|
MarkGUCPrefixReserved("sepgsql");
|
|
|
|
/* 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));
|
|
}
|