PostgreSQL Source Code  git master
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"
18 #include "catalog/objectaccess.h"
19 #include "catalog/pg_proc.h"
20 #include "executor/executor.h"
21 #include "fmgr.h"
22 #include "miscadmin.h"
23 #include "tcop/utility.h"
24 
26 
27 /*
28  * GUCs controlling which operations to deny
29  */
30 static bool REGRESS_deny_set_variable = false;
31 static bool REGRESS_deny_alter_system = false;
32 static bool REGRESS_deny_object_access = false;
33 static bool REGRESS_deny_exec_perms = false;
34 static bool REGRESS_deny_utility_commands = false;
35 static bool REGRESS_audit = false;
36 
37 /*
38  * GUCs for testing privileges on USERSET and SUSET variables,
39  * with and without privileges granted prior to module load.
40  */
41 static bool REGRESS_userset_variable1 = false;
42 static bool REGRESS_userset_variable2 = false;
43 static bool REGRESS_suset_variable1 = false;
44 static bool REGRESS_suset_variable2 = false;
45 
46 /* Saved hook values */
51 
52 /* Test Object Access Type Hook hooks */
54  Oid classId, const char *objName,
55  int subId, void *arg);
57  Oid objectId, int subId, void *arg);
58 static bool REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort);
59 static void REGRESS_utility_command(PlannedStmt *pstmt,
60  const char *queryString, bool readOnlyTree,
62  ParamListInfo params,
63  QueryEnvironment *queryEnv,
65 
66 /* Helper functions */
67 static char *accesstype_to_string(ObjectAccessType access, int subId);
69 
70 
71 /*
72  * Module load callback
73  */
74 void
75 _PG_init(void)
76 {
77  /*
78  * test_oat_hooks.deny_set_variable = (on|off)
79  */
80  DefineCustomBoolVariable("test_oat_hooks.deny_set_variable",
81  "Deny non-superuser set permissions",
82  NULL,
84  false,
85  PGC_SUSET,
87  NULL,
88  NULL,
89  NULL);
90 
91  /*
92  * test_oat_hooks.deny_alter_system = (on|off)
93  */
94  DefineCustomBoolVariable("test_oat_hooks.deny_alter_system",
95  "Deny non-superuser alter system set permissions",
96  NULL,
98  false,
99  PGC_SUSET,
101  NULL,
102  NULL,
103  NULL);
104 
105  /*
106  * test_oat_hooks.deny_object_access = (on|off)
107  */
108  DefineCustomBoolVariable("test_oat_hooks.deny_object_access",
109  "Deny non-superuser object access permissions",
110  NULL,
112  false,
113  PGC_SUSET,
115  NULL,
116  NULL,
117  NULL);
118 
119  /*
120  * test_oat_hooks.deny_exec_perms = (on|off)
121  */
122  DefineCustomBoolVariable("test_oat_hooks.deny_exec_perms",
123  "Deny non-superuser exec permissions",
124  NULL,
126  false,
127  PGC_SUSET,
129  NULL,
130  NULL,
131  NULL);
132 
133  /*
134  * test_oat_hooks.deny_utility_commands = (on|off)
135  */
136  DefineCustomBoolVariable("test_oat_hooks.deny_utility_commands",
137  "Deny non-superuser utility commands",
138  NULL,
140  false,
141  PGC_SUSET,
143  NULL,
144  NULL,
145  NULL);
146 
147  /*
148  * test_oat_hooks.audit = (on|off)
149  */
150  DefineCustomBoolVariable("test_oat_hooks.audit",
151  "Turn on/off debug audit messages",
152  NULL,
153  &REGRESS_audit,
154  false,
155  PGC_SUSET,
157  NULL,
158  NULL,
159  NULL);
160 
161  /*
162  * test_oat_hooks.user_var{1,2} = (on|off)
163  */
164  DefineCustomBoolVariable("test_oat_hooks.user_var1",
165  "Dummy parameter settable by public",
166  NULL,
168  false,
169  PGC_USERSET,
171  NULL,
172  NULL,
173  NULL);
174 
175  DefineCustomBoolVariable("test_oat_hooks.user_var2",
176  "Dummy parameter settable by public",
177  NULL,
179  false,
180  PGC_USERSET,
182  NULL,
183  NULL,
184  NULL);
185 
186  /*
187  * test_oat_hooks.super_var{1,2} = (on|off)
188  */
189  DefineCustomBoolVariable("test_oat_hooks.super_var1",
190  "Dummy parameter settable by superuser",
191  NULL,
193  false,
194  PGC_SUSET,
196  NULL,
197  NULL,
198  NULL);
199 
200  DefineCustomBoolVariable("test_oat_hooks.super_var2",
201  "Dummy parameter settable by superuser",
202  NULL,
204  false,
205  PGC_SUSET,
207  NULL,
208  NULL,
209  NULL);
210 
211  MarkGUCPrefixReserved("test_oat_hooks");
212 
213  /* Object access hook */
216 
217  /* Object access hook str */
220 
221  /* DML permission check */
224 
225  /* ProcessUtility hook */
228 }
229 
230 static void
231 emit_audit_message(const char *type, const char *hook, char *action, char *objName)
232 {
233  /*
234  * Ensure that audit messages are not duplicated by only emitting them
235  * from a leader process, not a worker process. This makes the test
236  * results deterministic even if run with debug_parallel_query = regress.
237  */
239  {
240  const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
241 
242  if (objName)
243  ereport(NOTICE,
244  (errcode(ERRCODE_INTERNAL_ERROR),
245  errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
246  else
247  ereport(NOTICE,
248  (errcode(ERRCODE_INTERNAL_ERROR),
249  errmsg("in %s: %s %s %s", hook, who, type, action)));
250  }
251 
252  if (action)
253  pfree(action);
254  if (objName)
255  pfree(objName);
256 }
257 
258 static void
259 audit_attempt(const char *hook, char *action, char *objName)
260 {
261  emit_audit_message("attempting", hook, action, objName);
262 }
263 
264 static void
265 audit_success(const char *hook, char *action, char *objName)
266 {
267  emit_audit_message("finished", hook, action, objName);
268 }
269 
270 static void
271 audit_failure(const char *hook, char *action, char *objName)
272 {
273  emit_audit_message("denied", hook, action, objName);
274 }
275 
276 static void
277 REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
278 {
279  audit_attempt("object_access_hook_str",
281  pstrdup(objName));
282 
284  {
285  (*next_object_access_hook_str) (access, classId, objName, subId, arg);
286  }
287 
288  switch (access)
289  {
290  case OAT_POST_ALTER:
291  if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
292  {
294  ereport(ERROR,
295  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
296  errmsg("permission denied: all privileges %s", objName)));
297  }
298  else if (subId & ACL_SET)
299  {
301  ereport(ERROR,
302  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
303  errmsg("permission denied: set %s", objName)));
304  }
305  else if (subId & ACL_ALTER_SYSTEM)
306  {
308  ereport(ERROR,
309  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
310  errmsg("permission denied: alter system set %s", objName)));
311  }
312  else
313  elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId);
314  break;
315  default:
316  break;
317  }
318 
319  audit_success("object_access_hook_str",
321  pstrdup(objName));
322 }
323 
324 static void
325 REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
326 {
327  audit_attempt("object access",
330 
332  ereport(ERROR,
333  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
334  errmsg("permission denied: %s [%s]",
337 
338  /* Forward to next hook in the chain */
340  (*next_object_access_hook) (access, classId, objectId, subId, arg);
341 
342  audit_success("object access",
345 }
346 
347 static bool
348 REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
349 {
350  bool am_super = superuser_arg(GetUserId());
351  bool allow = true;
352 
353  audit_attempt("executor check perms", pstrdup("execute"), NULL);
354 
355  /* Perform our check */
356  allow = !REGRESS_deny_exec_perms || am_super;
357  if (do_abort && !allow)
358  ereport(ERROR,
359  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
360  errmsg("permission denied: %s", "execute")));
361 
362  /* Forward to next hook in the chain */
364  !(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
365  allow = false;
366 
367  if (allow)
368  audit_success("executor check perms",
369  pstrdup("execute"),
370  NULL);
371  else
372  audit_failure("executor check perms",
373  pstrdup("execute"),
374  NULL);
375 
376  return allow;
377 }
378 
379 static void
381  const char *queryString,
382  bool readOnlyTree,
384  ParamListInfo params,
385  QueryEnvironment *queryEnv,
387  QueryCompletion *qc)
388 {
389  Node *parsetree = pstmt->utilityStmt;
390  const char *action = GetCommandTagName(CreateCommandTag(parsetree));
391 
392  audit_attempt("process utility",
393  pstrdup(action),
394  NULL);
395 
396  /* Check permissions */
398  ereport(ERROR,
399  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
400  errmsg("permission denied: %s", action)));
401 
402  /* Forward to next hook in the chain */
404  (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
405  context, params, queryEnv,
406  dest, qc);
407  else
408  standard_ProcessUtility(pstmt, queryString, readOnlyTree,
409  context, params, queryEnv,
410  dest, qc);
411 
412  /* We're done */
413  audit_success("process utility",
414  pstrdup(action),
415  NULL);
416 }
417 
418 static char *
420 {
421  const char *type;
422 
423  switch (access)
424  {
425  case OAT_POST_CREATE:
426  type = "create";
427  break;
428  case OAT_DROP:
429  type = "drop";
430  break;
431  case OAT_POST_ALTER:
432  type = "alter";
433  break;
435  type = "namespace search";
436  break;
438  type = "execute";
439  break;
440  case OAT_TRUNCATE:
441  type = "truncate";
442  break;
443  default:
444  type = "UNRECOGNIZED ObjectAccessType";
445  }
446 
447  if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
448  return psprintf("%s (subId=0x%x, all privileges)", type, subId);
449  if (subId & ACL_SET)
450  return psprintf("%s (subId=0x%x, set)", type, subId);
451  if (subId & ACL_ALTER_SYSTEM)
452  return psprintf("%s (subId=0x%x, alter system)", type, subId);
453 
454  return psprintf("%s (subId=0x%x)", type, subId);
455 }
456 
457 static char *
459 {
460  if (arg == NULL)
461  return pstrdup("extra info null");
462 
463  switch (access)
464  {
465  case OAT_POST_CREATE:
466  {
468 
469  return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
470  }
471  break;
472  case OAT_DROP:
473  {
474  ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
475 
476  return psprintf("%s%s%s%s%s%s",
477  ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
478  ? "internal action," : ""),
480  ? "concurrent drop," : ""),
481  ((drop_arg->dropflags & PERFORM_DELETION_QUIETLY)
482  ? "suppress notices," : ""),
484  ? "keep original object," : ""),
486  ? "keep extensions," : ""),
488  ? "normal concurrent drop," : ""));
489  }
490  break;
491  case OAT_POST_ALTER:
492  {
494 
495  return psprintf("%s %s auxiliary object",
496  (pa_arg->is_internal ? "internal" : "explicit"),
497  (OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
498  }
499  break;
501  {
503 
504  return psprintf("%s, %s",
505  (ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
506  (ns_arg->result ? "allowed" : "denied"));
507  }
508  break;
509  case OAT_TRUNCATE:
511  /* hook takes no arg. */
512  return pstrdup("unexpected extra info pointer received");
513  default:
514  return pstrdup("cannot parse extra info for unrecognized access type");
515  }
516 
517  return pstrdup("unknown");
518 }
#define OidIsValid(objectId)
Definition: c.h:766
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:72
bool(* ExecutorCheckPerms_hook_type)(List *rangeTable, List *rtePermInfos, bool ereport_on_violation)
Definition: executor.h:94
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:5090
void MarkGUCPrefixReserved(const char *className)
Definition: guc.c:5237
@ 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:514
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:46
tree context
Definition: radixtree.h:1835
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:540
CommandTag CreateCommandTag(Node *parsetree)
Definition: utility.c:2359
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