519 lines
14 KiB
C
519 lines
14 KiB
C
/*--------------------------------------------------------------------------
|
|
*
|
|
* test_oat_hooks.c
|
|
* Code for testing mandatory access control (MAC) using object access hooks.
|
|
*
|
|
* Copyright (c) 2015-2024, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* src/test/modules/test_oat_hooks/test_oat_hooks.c
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/parallel.h"
|
|
#include "catalog/dependency.h"
|
|
#include "catalog/objectaccess.h"
|
|
#include "catalog/pg_proc.h"
|
|
#include "executor/executor.h"
|
|
#include "fmgr.h"
|
|
#include "miscadmin.h"
|
|
#include "tcop/utility.h"
|
|
|
|
PG_MODULE_MAGIC;
|
|
|
|
/*
|
|
* GUCs controlling which operations to deny
|
|
*/
|
|
static bool REGRESS_deny_set_variable = false;
|
|
static bool REGRESS_deny_alter_system = false;
|
|
static bool REGRESS_deny_object_access = false;
|
|
static bool REGRESS_deny_exec_perms = false;
|
|
static bool REGRESS_deny_utility_commands = false;
|
|
static bool REGRESS_audit = false;
|
|
|
|
/*
|
|
* GUCs for testing privileges on USERSET and SUSET variables,
|
|
* with and without privileges granted prior to module load.
|
|
*/
|
|
static bool REGRESS_userset_variable1 = false;
|
|
static bool REGRESS_userset_variable2 = false;
|
|
static bool REGRESS_suset_variable1 = false;
|
|
static bool REGRESS_suset_variable2 = false;
|
|
|
|
/* Saved hook values */
|
|
static object_access_hook_type next_object_access_hook = NULL;
|
|
static object_access_hook_type_str next_object_access_hook_str = NULL;
|
|
static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
|
|
static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
|
|
|
|
/* Test Object Access Type Hook hooks */
|
|
static void REGRESS_object_access_hook_str(ObjectAccessType access,
|
|
Oid classId, const char *objName,
|
|
int subId, void *arg);
|
|
static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
|
|
Oid objectId, int subId, void *arg);
|
|
static bool REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort);
|
|
static void REGRESS_utility_command(PlannedStmt *pstmt,
|
|
const char *queryString, bool readOnlyTree,
|
|
ProcessUtilityContext context,
|
|
ParamListInfo params,
|
|
QueryEnvironment *queryEnv,
|
|
DestReceiver *dest, QueryCompletion *qc);
|
|
|
|
/* Helper functions */
|
|
static char *accesstype_to_string(ObjectAccessType access, int subId);
|
|
static char *accesstype_arg_to_string(ObjectAccessType access, void *arg);
|
|
|
|
|
|
/*
|
|
* Module load callback
|
|
*/
|
|
void
|
|
_PG_init(void)
|
|
{
|
|
/*
|
|
* test_oat_hooks.deny_set_variable = (on|off)
|
|
*/
|
|
DefineCustomBoolVariable("test_oat_hooks.deny_set_variable",
|
|
"Deny non-superuser set permissions",
|
|
NULL,
|
|
®RESS_deny_set_variable,
|
|
false,
|
|
PGC_SUSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
/*
|
|
* test_oat_hooks.deny_alter_system = (on|off)
|
|
*/
|
|
DefineCustomBoolVariable("test_oat_hooks.deny_alter_system",
|
|
"Deny non-superuser alter system set permissions",
|
|
NULL,
|
|
®RESS_deny_alter_system,
|
|
false,
|
|
PGC_SUSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
/*
|
|
* test_oat_hooks.deny_object_access = (on|off)
|
|
*/
|
|
DefineCustomBoolVariable("test_oat_hooks.deny_object_access",
|
|
"Deny non-superuser object access permissions",
|
|
NULL,
|
|
®RESS_deny_object_access,
|
|
false,
|
|
PGC_SUSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
/*
|
|
* test_oat_hooks.deny_exec_perms = (on|off)
|
|
*/
|
|
DefineCustomBoolVariable("test_oat_hooks.deny_exec_perms",
|
|
"Deny non-superuser exec permissions",
|
|
NULL,
|
|
®RESS_deny_exec_perms,
|
|
false,
|
|
PGC_SUSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
/*
|
|
* test_oat_hooks.deny_utility_commands = (on|off)
|
|
*/
|
|
DefineCustomBoolVariable("test_oat_hooks.deny_utility_commands",
|
|
"Deny non-superuser utility commands",
|
|
NULL,
|
|
®RESS_deny_utility_commands,
|
|
false,
|
|
PGC_SUSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
/*
|
|
* test_oat_hooks.audit = (on|off)
|
|
*/
|
|
DefineCustomBoolVariable("test_oat_hooks.audit",
|
|
"Turn on/off debug audit messages",
|
|
NULL,
|
|
®RESS_audit,
|
|
false,
|
|
PGC_SUSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
/*
|
|
* test_oat_hooks.user_var{1,2} = (on|off)
|
|
*/
|
|
DefineCustomBoolVariable("test_oat_hooks.user_var1",
|
|
"Dummy parameter settable by public",
|
|
NULL,
|
|
®RESS_userset_variable1,
|
|
false,
|
|
PGC_USERSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
DefineCustomBoolVariable("test_oat_hooks.user_var2",
|
|
"Dummy parameter settable by public",
|
|
NULL,
|
|
®RESS_userset_variable2,
|
|
false,
|
|
PGC_USERSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
/*
|
|
* test_oat_hooks.super_var{1,2} = (on|off)
|
|
*/
|
|
DefineCustomBoolVariable("test_oat_hooks.super_var1",
|
|
"Dummy parameter settable by superuser",
|
|
NULL,
|
|
®RESS_suset_variable1,
|
|
false,
|
|
PGC_SUSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
DefineCustomBoolVariable("test_oat_hooks.super_var2",
|
|
"Dummy parameter settable by superuser",
|
|
NULL,
|
|
®RESS_suset_variable2,
|
|
false,
|
|
PGC_SUSET,
|
|
GUC_NOT_IN_SAMPLE,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
MarkGUCPrefixReserved("test_oat_hooks");
|
|
|
|
/* Object access hook */
|
|
next_object_access_hook = object_access_hook;
|
|
object_access_hook = REGRESS_object_access_hook;
|
|
|
|
/* Object access hook str */
|
|
next_object_access_hook_str = object_access_hook_str;
|
|
object_access_hook_str = REGRESS_object_access_hook_str;
|
|
|
|
/* DML permission check */
|
|
next_exec_check_perms_hook = ExecutorCheckPerms_hook;
|
|
ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
|
|
|
|
/* ProcessUtility hook */
|
|
next_ProcessUtility_hook = ProcessUtility_hook;
|
|
ProcessUtility_hook = REGRESS_utility_command;
|
|
}
|
|
|
|
static void
|
|
emit_audit_message(const char *type, const char *hook, char *action, char *objName)
|
|
{
|
|
/*
|
|
* Ensure that audit messages are not duplicated by only emitting them
|
|
* from a leader process, not a worker process. This makes the test
|
|
* results deterministic even if run with debug_parallel_query = regress.
|
|
*/
|
|
if (REGRESS_audit && !IsParallelWorker())
|
|
{
|
|
const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
|
|
|
|
if (objName)
|
|
ereport(NOTICE,
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
|
|
else
|
|
ereport(NOTICE,
|
|
(errcode(ERRCODE_INTERNAL_ERROR),
|
|
errmsg("in %s: %s %s %s", hook, who, type, action)));
|
|
}
|
|
|
|
if (action)
|
|
pfree(action);
|
|
if (objName)
|
|
pfree(objName);
|
|
}
|
|
|
|
static void
|
|
audit_attempt(const char *hook, char *action, char *objName)
|
|
{
|
|
emit_audit_message("attempting", hook, action, objName);
|
|
}
|
|
|
|
static void
|
|
audit_success(const char *hook, char *action, char *objName)
|
|
{
|
|
emit_audit_message("finished", hook, action, objName);
|
|
}
|
|
|
|
static void
|
|
audit_failure(const char *hook, char *action, char *objName)
|
|
{
|
|
emit_audit_message("denied", hook, action, objName);
|
|
}
|
|
|
|
static void
|
|
REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
|
|
{
|
|
audit_attempt("object_access_hook_str",
|
|
accesstype_to_string(access, subId),
|
|
pstrdup(objName));
|
|
|
|
if (next_object_access_hook_str)
|
|
{
|
|
(*next_object_access_hook_str) (access, classId, objName, subId, arg);
|
|
}
|
|
|
|
switch (access)
|
|
{
|
|
case OAT_POST_ALTER:
|
|
if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
|
|
{
|
|
if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: all privileges %s", objName)));
|
|
}
|
|
else if (subId & ACL_SET)
|
|
{
|
|
if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: set %s", objName)));
|
|
}
|
|
else if (subId & ACL_ALTER_SYSTEM)
|
|
{
|
|
if (REGRESS_deny_alter_system && !superuser_arg(GetUserId()))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: alter system set %s", objName)));
|
|
}
|
|
else
|
|
elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
audit_success("object_access_hook_str",
|
|
accesstype_to_string(access, subId),
|
|
pstrdup(objName));
|
|
}
|
|
|
|
static void
|
|
REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
|
|
{
|
|
audit_attempt("object access",
|
|
accesstype_to_string(access, 0),
|
|
accesstype_arg_to_string(access, arg));
|
|
|
|
if (REGRESS_deny_object_access && !superuser_arg(GetUserId()))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: %s [%s]",
|
|
accesstype_to_string(access, 0),
|
|
accesstype_arg_to_string(access, arg))));
|
|
|
|
/* Forward to next hook in the chain */
|
|
if (next_object_access_hook)
|
|
(*next_object_access_hook) (access, classId, objectId, subId, arg);
|
|
|
|
audit_success("object access",
|
|
accesstype_to_string(access, 0),
|
|
accesstype_arg_to_string(access, arg));
|
|
}
|
|
|
|
static bool
|
|
REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
|
|
{
|
|
bool am_super = superuser_arg(GetUserId());
|
|
bool allow = true;
|
|
|
|
audit_attempt("executor check perms", pstrdup("execute"), NULL);
|
|
|
|
/* Perform our check */
|
|
allow = !REGRESS_deny_exec_perms || am_super;
|
|
if (do_abort && !allow)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: %s", "execute")));
|
|
|
|
/* Forward to next hook in the chain */
|
|
if (next_exec_check_perms_hook &&
|
|
!(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
|
|
allow = false;
|
|
|
|
if (allow)
|
|
audit_success("executor check perms",
|
|
pstrdup("execute"),
|
|
NULL);
|
|
else
|
|
audit_failure("executor check perms",
|
|
pstrdup("execute"),
|
|
NULL);
|
|
|
|
return allow;
|
|
}
|
|
|
|
static void
|
|
REGRESS_utility_command(PlannedStmt *pstmt,
|
|
const char *queryString,
|
|
bool readOnlyTree,
|
|
ProcessUtilityContext context,
|
|
ParamListInfo params,
|
|
QueryEnvironment *queryEnv,
|
|
DestReceiver *dest,
|
|
QueryCompletion *qc)
|
|
{
|
|
Node *parsetree = pstmt->utilityStmt;
|
|
const char *action = GetCommandTagName(CreateCommandTag(parsetree));
|
|
|
|
audit_attempt("process utility",
|
|
pstrdup(action),
|
|
NULL);
|
|
|
|
/* Check permissions */
|
|
if (REGRESS_deny_utility_commands && !superuser_arg(GetUserId()))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: %s", action)));
|
|
|
|
/* Forward to next hook in the chain */
|
|
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);
|
|
|
|
/* We're done */
|
|
audit_success("process utility",
|
|
pstrdup(action),
|
|
NULL);
|
|
}
|
|
|
|
static char *
|
|
accesstype_to_string(ObjectAccessType access, int subId)
|
|
{
|
|
const char *type;
|
|
|
|
switch (access)
|
|
{
|
|
case OAT_POST_CREATE:
|
|
type = "create";
|
|
break;
|
|
case OAT_DROP:
|
|
type = "drop";
|
|
break;
|
|
case OAT_POST_ALTER:
|
|
type = "alter";
|
|
break;
|
|
case OAT_NAMESPACE_SEARCH:
|
|
type = "namespace search";
|
|
break;
|
|
case OAT_FUNCTION_EXECUTE:
|
|
type = "execute";
|
|
break;
|
|
case OAT_TRUNCATE:
|
|
type = "truncate";
|
|
break;
|
|
default:
|
|
type = "UNRECOGNIZED ObjectAccessType";
|
|
}
|
|
|
|
if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
|
|
return psprintf("%s (subId=0x%x, all privileges)", type, subId);
|
|
if (subId & ACL_SET)
|
|
return psprintf("%s (subId=0x%x, set)", type, subId);
|
|
if (subId & ACL_ALTER_SYSTEM)
|
|
return psprintf("%s (subId=0x%x, alter system)", type, subId);
|
|
|
|
return psprintf("%s (subId=0x%x)", type, subId);
|
|
}
|
|
|
|
static char *
|
|
accesstype_arg_to_string(ObjectAccessType access, void *arg)
|
|
{
|
|
if (arg == NULL)
|
|
return pstrdup("extra info null");
|
|
|
|
switch (access)
|
|
{
|
|
case OAT_POST_CREATE:
|
|
{
|
|
ObjectAccessPostCreate *pc_arg = (ObjectAccessPostCreate *) arg;
|
|
|
|
return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
|
|
}
|
|
break;
|
|
case OAT_DROP:
|
|
{
|
|
ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
|
|
|
|
return psprintf("%s%s%s%s%s%s",
|
|
((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
|
|
? "internal action," : ""),
|
|
((drop_arg->dropflags & PERFORM_DELETION_CONCURRENTLY)
|
|
? "concurrent drop," : ""),
|
|
((drop_arg->dropflags & PERFORM_DELETION_QUIETLY)
|
|
? "suppress notices," : ""),
|
|
((drop_arg->dropflags & PERFORM_DELETION_SKIP_ORIGINAL)
|
|
? "keep original object," : ""),
|
|
((drop_arg->dropflags & PERFORM_DELETION_SKIP_EXTENSIONS)
|
|
? "keep extensions," : ""),
|
|
((drop_arg->dropflags & PERFORM_DELETION_CONCURRENT_LOCK)
|
|
? "normal concurrent drop," : ""));
|
|
}
|
|
break;
|
|
case OAT_POST_ALTER:
|
|
{
|
|
ObjectAccessPostAlter *pa_arg = (ObjectAccessPostAlter *) arg;
|
|
|
|
return psprintf("%s %s auxiliary object",
|
|
(pa_arg->is_internal ? "internal" : "explicit"),
|
|
(OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
|
|
}
|
|
break;
|
|
case OAT_NAMESPACE_SEARCH:
|
|
{
|
|
ObjectAccessNamespaceSearch *ns_arg = (ObjectAccessNamespaceSearch *) arg;
|
|
|
|
return psprintf("%s, %s",
|
|
(ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
|
|
(ns_arg->result ? "allowed" : "denied"));
|
|
}
|
|
break;
|
|
case OAT_TRUNCATE:
|
|
case OAT_FUNCTION_EXECUTE:
|
|
/* hook takes no arg. */
|
|
return pstrdup("unexpected extra info pointer received");
|
|
default:
|
|
return pstrdup("cannot parse extra info for unrecognized access type");
|
|
}
|
|
|
|
return pstrdup("unknown");
|
|
}
|