PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
test_oat_hooks.c
Go to the documentation of this file.
1/*--------------------------------------------------------------------------
2 *
3 * test_oat_hooks.c
4 * Code for testing mandatory access control (MAC) using object access hooks.
5 *
6 * Copyright (c) 2015-2024, PostgreSQL Global Development Group
7 *
8 * IDENTIFICATION
9 * src/test/modules/test_oat_hooks/test_oat_hooks.c
10 *
11 * -------------------------------------------------------------------------
12 */
13
14#include "postgres.h"
15
16#include "access/parallel.h"
17#include "catalog/dependency.h"
19#include "executor/executor.h"
20#include "fmgr.h"
21#include "miscadmin.h"
22#include "tcop/utility.h"
23
25
26/*
27 * GUCs controlling which operations to deny
28 */
29static bool REGRESS_deny_set_variable = false;
30static bool REGRESS_deny_alter_system = false;
31static bool REGRESS_deny_object_access = false;
32static bool REGRESS_deny_exec_perms = false;
34static bool REGRESS_audit = false;
35
36/*
37 * GUCs for testing privileges on USERSET and SUSET variables,
38 * with and without privileges granted prior to module load.
39 */
40static bool REGRESS_userset_variable1 = false;
41static bool REGRESS_userset_variable2 = false;
42static bool REGRESS_suset_variable1 = false;
43static bool REGRESS_suset_variable2 = false;
44
45/* Saved hook values */
50
51/* Test Object Access Type Hook hooks */
53 Oid classId, const char *objName,
54 int subId, void *arg);
56 Oid objectId, int subId, void *arg);
57static bool REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort);
58static void REGRESS_utility_command(PlannedStmt *pstmt,
59 const char *queryString, bool readOnlyTree,
61 ParamListInfo params,
62 QueryEnvironment *queryEnv,
64
65/* Helper functions */
66static char *accesstype_to_string(ObjectAccessType access, int subId);
68
69
70/*
71 * Module load callback
72 */
73void
75{
76 /*
77 * test_oat_hooks.deny_set_variable = (on|off)
78 */
79 DefineCustomBoolVariable("test_oat_hooks.deny_set_variable",
80 "Deny non-superuser set permissions",
81 NULL,
83 false,
86 NULL,
87 NULL,
88 NULL);
89
90 /*
91 * test_oat_hooks.deny_alter_system = (on|off)
92 */
93 DefineCustomBoolVariable("test_oat_hooks.deny_alter_system",
94 "Deny non-superuser alter system set permissions",
95 NULL,
97 false,
100 NULL,
101 NULL,
102 NULL);
103
104 /*
105 * test_oat_hooks.deny_object_access = (on|off)
106 */
107 DefineCustomBoolVariable("test_oat_hooks.deny_object_access",
108 "Deny non-superuser object access permissions",
109 NULL,
111 false,
112 PGC_SUSET,
114 NULL,
115 NULL,
116 NULL);
117
118 /*
119 * test_oat_hooks.deny_exec_perms = (on|off)
120 */
121 DefineCustomBoolVariable("test_oat_hooks.deny_exec_perms",
122 "Deny non-superuser exec permissions",
123 NULL,
125 false,
126 PGC_SUSET,
128 NULL,
129 NULL,
130 NULL);
131
132 /*
133 * test_oat_hooks.deny_utility_commands = (on|off)
134 */
135 DefineCustomBoolVariable("test_oat_hooks.deny_utility_commands",
136 "Deny non-superuser utility commands",
137 NULL,
139 false,
140 PGC_SUSET,
142 NULL,
143 NULL,
144 NULL);
145
146 /*
147 * test_oat_hooks.audit = (on|off)
148 */
149 DefineCustomBoolVariable("test_oat_hooks.audit",
150 "Turn on/off debug audit messages",
151 NULL,
153 false,
154 PGC_SUSET,
156 NULL,
157 NULL,
158 NULL);
159
160 /*
161 * test_oat_hooks.user_var{1,2} = (on|off)
162 */
163 DefineCustomBoolVariable("test_oat_hooks.user_var1",
164 "Dummy parameter settable by public",
165 NULL,
167 false,
170 NULL,
171 NULL,
172 NULL);
173
174 DefineCustomBoolVariable("test_oat_hooks.user_var2",
175 "Dummy parameter settable by public",
176 NULL,
178 false,
181 NULL,
182 NULL,
183 NULL);
184
185 /*
186 * test_oat_hooks.super_var{1,2} = (on|off)
187 */
188 DefineCustomBoolVariable("test_oat_hooks.super_var1",
189 "Dummy parameter settable by superuser",
190 NULL,
192 false,
193 PGC_SUSET,
195 NULL,
196 NULL,
197 NULL);
198
199 DefineCustomBoolVariable("test_oat_hooks.super_var2",
200 "Dummy parameter settable by superuser",
201 NULL,
203 false,
204 PGC_SUSET,
206 NULL,
207 NULL,
208 NULL);
209
210 MarkGUCPrefixReserved("test_oat_hooks");
211
212 /* Object access hook */
215
216 /* Object access hook str */
219
220 /* DML permission check */
223
224 /* ProcessUtility hook */
227}
228
229static void
230emit_audit_message(const char *type, const char *hook, char *action, char *objName)
231{
232 /*
233 * Ensure that audit messages are not duplicated by only emitting them
234 * from a leader process, not a worker process. This makes the test
235 * results deterministic even if run with debug_parallel_query = regress.
236 */
238 {
239 const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
240
241 if (objName)
243 (errcode(ERRCODE_INTERNAL_ERROR),
244 errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
245 else
247 (errcode(ERRCODE_INTERNAL_ERROR),
248 errmsg("in %s: %s %s %s", hook, who, type, action)));
249 }
250
251 if (action)
252 pfree(action);
253 if (objName)
254 pfree(objName);
255}
256
257static void
258audit_attempt(const char *hook, char *action, char *objName)
259{
260 emit_audit_message("attempting", hook, action, objName);
261}
262
263static void
264audit_success(const char *hook, char *action, char *objName)
265{
266 emit_audit_message("finished", hook, action, objName);
267}
268
269static void
270audit_failure(const char *hook, char *action, char *objName)
271{
272 emit_audit_message("denied", hook, action, objName);
273}
274
275static void
276REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
277{
278 audit_attempt("object_access_hook_str",
280 pstrdup(objName));
281
283 {
284 (*next_object_access_hook_str) (access, classId, objName, subId, arg);
285 }
286
287 switch (access)
288 {
289 case OAT_POST_ALTER:
290 if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
291 {
294 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
295 errmsg("permission denied: all privileges %s", objName)));
296 }
297 else if (subId & ACL_SET)
298 {
301 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
302 errmsg("permission denied: set %s", objName)));
303 }
304 else if (subId & ACL_ALTER_SYSTEM)
305 {
308 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
309 errmsg("permission denied: alter system set %s", objName)));
310 }
311 else
312 elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId);
313 break;
314 default:
315 break;
316 }
317
318 audit_success("object_access_hook_str",
320 pstrdup(objName));
321}
322
323static void
324REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
325{
326 audit_attempt("object access",
329
332 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
333 errmsg("permission denied: %s [%s]",
336
337 /* Forward to next hook in the chain */
339 (*next_object_access_hook) (access, classId, objectId, subId, arg);
340
341 audit_success("object access",
344}
345
346static bool
347REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
348{
349 bool am_super = superuser_arg(GetUserId());
350 bool allow = true;
351
352 audit_attempt("executor check perms", pstrdup("execute"), NULL);
353
354 /* Perform our check */
355 allow = !REGRESS_deny_exec_perms || am_super;
356 if (do_abort && !allow)
358 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
359 errmsg("permission denied: %s", "execute")));
360
361 /* Forward to next hook in the chain */
363 !(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
364 allow = false;
365
366 if (allow)
367 audit_success("executor check perms",
368 pstrdup("execute"),
369 NULL);
370 else
371 audit_failure("executor check perms",
372 pstrdup("execute"),
373 NULL);
374
375 return allow;
376}
377
378static void
380 const char *queryString,
381 bool readOnlyTree,
383 ParamListInfo params,
384 QueryEnvironment *queryEnv,
386 QueryCompletion *qc)
387{
388 Node *parsetree = pstmt->utilityStmt;
389 const char *action = GetCommandTagName(CreateCommandTag(parsetree));
390
391 audit_attempt("process utility",
393 NULL);
394
395 /* Check permissions */
398 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
399 errmsg("permission denied: %s", action)));
400
401 /* Forward to next hook in the chain */
403 (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
404 context, params, queryEnv,
405 dest, qc);
406 else
407 standard_ProcessUtility(pstmt, queryString, readOnlyTree,
408 context, params, queryEnv,
409 dest, qc);
410
411 /* We're done */
412 audit_success("process utility",
414 NULL);
415}
416
417static char *
419{
420 const char *type;
421
422 switch (access)
423 {
424 case OAT_POST_CREATE:
425 type = "create";
426 break;
427 case OAT_DROP:
428 type = "drop";
429 break;
430 case OAT_POST_ALTER:
431 type = "alter";
432 break;
434 type = "namespace search";
435 break;
437 type = "execute";
438 break;
439 case OAT_TRUNCATE:
440 type = "truncate";
441 break;
442 default:
443 type = "UNRECOGNIZED ObjectAccessType";
444 }
445
446 if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
447 return psprintf("%s (subId=0x%x, all privileges)", type, subId);
448 if (subId & ACL_SET)
449 return psprintf("%s (subId=0x%x, set)", type, subId);
450 if (subId & ACL_ALTER_SYSTEM)
451 return psprintf("%s (subId=0x%x, alter system)", type, subId);
452
453 return psprintf("%s (subId=0x%x)", type, subId);
454}
455
456static char *
458{
459 if (arg == NULL)
460 return pstrdup("extra info null");
461
462 switch (access)
463 {
464 case OAT_POST_CREATE:
465 {
467
468 return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
469 }
470 break;
471 case OAT_DROP:
472 {
473 ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
474
475 return psprintf("%s%s%s%s%s%s",
477 ? "internal action," : ""),
479 ? "concurrent drop," : ""),
481 ? "suppress notices," : ""),
483 ? "keep original object," : ""),
485 ? "keep extensions," : ""),
487 ? "normal concurrent drop," : ""));
488 }
489 break;
490 case OAT_POST_ALTER:
491 {
493
494 return psprintf("%s %s auxiliary object",
495 (pa_arg->is_internal ? "internal" : "explicit"),
496 (OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
497 }
498 break;
500 {
502
503 return psprintf("%s, %s",
504 (ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
505 (ns_arg->result ? "allowed" : "denied"));
506 }
507 break;
508 case OAT_TRUNCATE:
510 /* hook takes no arg. */
511 return pstrdup("unexpected extra info pointer received");
512 default:
513 return pstrdup("cannot parse extra info for unrecognized access type");
514 }
515
516 return pstrdup("unknown");
517}
#define OidIsValid(objectId)
Definition: c.h:729
const char * GetCommandTagName(CommandTag commandTag)
Definition: cmdtag.c:47
#define PERFORM_DELETION_CONCURRENTLY
Definition: dependency.h:93
#define PERFORM_DELETION_SKIP_EXTENSIONS
Definition: dependency.h:96
#define PERFORM_DELETION_CONCURRENT_LOCK
Definition: dependency.h:97
#define PERFORM_DELETION_QUIETLY
Definition: dependency.h:94
#define PERFORM_DELETION_SKIP_ORIGINAL
Definition: dependency.h:95
#define PERFORM_DELETION_INTERNAL
Definition: dependency.h:92
int errcode(int sqlerrcode)
Definition: elog.c:853
int errmsg(const char *fmt,...)
Definition: elog.c:1070
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:225
#define NOTICE
Definition: elog.h:35
#define ereport(elevel,...)
Definition: elog.h:149
ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook
Definition: execMain.c:73
bool(* ExecutorCheckPerms_hook_type)(List *rangeTable, List *rtePermInfos, bool ereport_on_violation)
Definition: executor.h:93
void DefineCustomBoolVariable(const char *name, const char *short_desc, const char *long_desc, bool *valueAddr, bool bootValue, GucContext context, int flags, GucBoolCheckHook check_hook, GucBoolAssignHook assign_hook, GucShowHook show_hook)
Definition: guc.c:5132
void MarkGUCPrefixReserved(const char *className)
Definition: guc.c:5279
@ PGC_SUSET
Definition: guc.h:74
@ PGC_USERSET
Definition: guc.h:75
#define GUC_NOT_IN_SAMPLE
Definition: guc.h:217
#define IsParallelWorker()
Definition: parallel.h:60
char * pstrdup(const char *in)
Definition: mcxt.c:1696
void pfree(void *pointer)
Definition: mcxt.c:1521
Oid GetUserId(void)
Definition: miscinit.c:517
object_access_hook_type object_access_hook
Definition: objectaccess.c:22
object_access_hook_type_str object_access_hook_str
Definition: objectaccess.c:23
void(* object_access_hook_type)(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
Definition: objectaccess.h:127
void(* object_access_hook_type_str)(ObjectAccessType access, Oid classId, const char *objectStr, int subId, void *arg)
Definition: objectaccess.h:133
ObjectAccessType
Definition: objectaccess.h:49
@ OAT_NAMESPACE_SEARCH
Definition: objectaccess.h:53
@ OAT_FUNCTION_EXECUTE
Definition: objectaccess.h:54
@ OAT_DROP
Definition: objectaccess.h:51
@ OAT_TRUNCATE
Definition: objectaccess.h:55
@ OAT_POST_ALTER
Definition: objectaccess.h:52
@ OAT_POST_CREATE
Definition: objectaccess.h:50
#define ACL_SET
Definition: parsenodes.h:88
#define ACL_ALTER_SYSTEM
Definition: parsenodes.h:89
void * arg
unsigned int Oid
Definition: postgres_ext.h:31
short access
Definition: preproc-type.c:36
char * psprintf(const char *fmt,...)
Definition: psprintf.c:43
tree context
Definition: radixtree.h:1837
Definition: pg_list.h:54
Definition: nodes.h:129
Node * utilityStmt
Definition: plannodes.h:95
bool superuser_arg(Oid roleid)
Definition: superuser.c:56
static bool REGRESS_suset_variable1
static bool REGRESS_deny_exec_perms
static bool REGRESS_deny_object_access
static void REGRESS_utility_command(PlannedStmt *pstmt, const char *queryString, bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc)
void _PG_init(void)
static ExecutorCheckPerms_hook_type next_exec_check_perms_hook
static ProcessUtility_hook_type next_ProcessUtility_hook
static void emit_audit_message(const char *type, const char *hook, char *action, char *objName)
static bool REGRESS_deny_set_variable
static bool REGRESS_userset_variable2
PG_MODULE_MAGIC
static char * accesstype_arg_to_string(ObjectAccessType access, void *arg)
static bool REGRESS_deny_alter_system
static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
static void audit_attempt(const char *hook, char *action, char *objName)
static char * accesstype_to_string(ObjectAccessType access, int subId)
static bool REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
static bool REGRESS_userset_variable1
static void audit_failure(const char *hook, char *action, char *objName)
static bool REGRESS_deny_utility_commands
static object_access_hook_type next_object_access_hook
static object_access_hook_type_str next_object_access_hook_str
static bool REGRESS_suset_variable2
static void REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
static void audit_success(const char *hook, char *action, char *objName)
static bool REGRESS_audit
void standard_ProcessUtility(PlannedStmt *pstmt, const char *queryString, bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc)
Definition: utility.c:543
CommandTag CreateCommandTag(Node *parsetree)
Definition: utility.c:2362
ProcessUtility_hook_type ProcessUtility_hook
Definition: utility.c:70
void(* ProcessUtility_hook_type)(PlannedStmt *pstmt, const char *queryString, bool readOnlyTree, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc)
Definition: utility.h:71
ProcessUtilityContext
Definition: utility.h:21
const char * type