Age Owner Branch data TLA Line data Source code
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 : :
754 andrew@dunslane.net 25 :CBC 2 : PG_MODULE_MAGIC;
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 */
47 : : static object_access_hook_type next_object_access_hook = NULL;
48 : : static object_access_hook_type_str next_object_access_hook_str = NULL;
49 : : static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
50 : : static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
51 : :
52 : : /* Test Object Access Type Hook hooks */
53 : : static void REGRESS_object_access_hook_str(ObjectAccessType access,
54 : : Oid classId, const char *objName,
55 : : int subId, void *arg);
56 : : static void REGRESS_object_access_hook(ObjectAccessType access, Oid classId,
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,
61 : : ProcessUtilityContext context,
62 : : ParamListInfo params,
63 : : QueryEnvironment *queryEnv,
64 : : DestReceiver *dest, QueryCompletion *qc);
65 : :
66 : : /* Helper functions */
67 : : static char *accesstype_to_string(ObjectAccessType access, int subId);
68 : : static char *accesstype_arg_to_string(ObjectAccessType access, void *arg);
69 : :
70 : :
71 : : /*
72 : : * Module load callback
73 : : */
74 : : void
75 : 2 : _PG_init(void)
76 : : {
77 : : /*
78 : : * test_oat_hooks.deny_set_variable = (on|off)
79 : : */
80 : 2 : DefineCustomBoolVariable("test_oat_hooks.deny_set_variable",
81 : : "Deny non-superuser set permissions",
82 : : NULL,
83 : : ®RESS_deny_set_variable,
84 : : false,
85 : : PGC_SUSET,
86 : : GUC_NOT_IN_SAMPLE,
87 : : NULL,
88 : : NULL,
89 : : NULL);
90 : :
91 : : /*
92 : : * test_oat_hooks.deny_alter_system = (on|off)
93 : : */
94 : 2 : DefineCustomBoolVariable("test_oat_hooks.deny_alter_system",
95 : : "Deny non-superuser alter system set permissions",
96 : : NULL,
97 : : ®RESS_deny_alter_system,
98 : : false,
99 : : PGC_SUSET,
100 : : GUC_NOT_IN_SAMPLE,
101 : : NULL,
102 : : NULL,
103 : : NULL);
104 : :
105 : : /*
106 : : * test_oat_hooks.deny_object_access = (on|off)
107 : : */
108 : 2 : DefineCustomBoolVariable("test_oat_hooks.deny_object_access",
109 : : "Deny non-superuser object access permissions",
110 : : NULL,
111 : : ®RESS_deny_object_access,
112 : : false,
113 : : PGC_SUSET,
114 : : GUC_NOT_IN_SAMPLE,
115 : : NULL,
116 : : NULL,
117 : : NULL);
118 : :
119 : : /*
120 : : * test_oat_hooks.deny_exec_perms = (on|off)
121 : : */
122 : 2 : DefineCustomBoolVariable("test_oat_hooks.deny_exec_perms",
123 : : "Deny non-superuser exec permissions",
124 : : NULL,
125 : : ®RESS_deny_exec_perms,
126 : : false,
127 : : PGC_SUSET,
128 : : GUC_NOT_IN_SAMPLE,
129 : : NULL,
130 : : NULL,
131 : : NULL);
132 : :
133 : : /*
134 : : * test_oat_hooks.deny_utility_commands = (on|off)
135 : : */
136 : 2 : DefineCustomBoolVariable("test_oat_hooks.deny_utility_commands",
137 : : "Deny non-superuser utility commands",
138 : : NULL,
139 : : ®RESS_deny_utility_commands,
140 : : false,
141 : : PGC_SUSET,
142 : : GUC_NOT_IN_SAMPLE,
143 : : NULL,
144 : : NULL,
145 : : NULL);
146 : :
147 : : /*
148 : : * test_oat_hooks.audit = (on|off)
149 : : */
150 : 2 : DefineCustomBoolVariable("test_oat_hooks.audit",
151 : : "Turn on/off debug audit messages",
152 : : NULL,
153 : : ®RESS_audit,
154 : : false,
155 : : PGC_SUSET,
156 : : GUC_NOT_IN_SAMPLE,
157 : : NULL,
158 : : NULL,
159 : : NULL);
160 : :
161 : : /*
162 : : * test_oat_hooks.user_var{1,2} = (on|off)
163 : : */
739 tgl@sss.pgh.pa.us 164 : 2 : DefineCustomBoolVariable("test_oat_hooks.user_var1",
165 : : "Dummy parameter settable by public",
166 : : NULL,
167 : : ®RESS_userset_variable1,
168 : : false,
169 : : PGC_USERSET,
170 : : GUC_NOT_IN_SAMPLE,
171 : : NULL,
172 : : NULL,
173 : : NULL);
174 : :
175 : 2 : DefineCustomBoolVariable("test_oat_hooks.user_var2",
176 : : "Dummy parameter settable by public",
177 : : NULL,
178 : : ®RESS_userset_variable2,
179 : : false,
180 : : PGC_USERSET,
181 : : GUC_NOT_IN_SAMPLE,
182 : : NULL,
183 : : NULL,
184 : : NULL);
185 : :
186 : : /*
187 : : * test_oat_hooks.super_var{1,2} = (on|off)
188 : : */
189 : 2 : DefineCustomBoolVariable("test_oat_hooks.super_var1",
190 : : "Dummy parameter settable by superuser",
191 : : NULL,
192 : : ®RESS_suset_variable1,
193 : : false,
194 : : PGC_SUSET,
195 : : GUC_NOT_IN_SAMPLE,
196 : : NULL,
197 : : NULL,
198 : : NULL);
199 : :
200 : 2 : DefineCustomBoolVariable("test_oat_hooks.super_var2",
201 : : "Dummy parameter settable by superuser",
202 : : NULL,
203 : : ®RESS_suset_variable2,
204 : : false,
205 : : PGC_SUSET,
206 : : GUC_NOT_IN_SAMPLE,
207 : : NULL,
208 : : NULL,
209 : : NULL);
210 : :
754 andrew@dunslane.net 211 : 2 : MarkGUCPrefixReserved("test_oat_hooks");
212 : :
213 : : /* Object access hook */
214 : 2 : next_object_access_hook = object_access_hook;
215 : 2 : object_access_hook = REGRESS_object_access_hook;
216 : :
217 : : /* Object access hook str */
218 : 2 : next_object_access_hook_str = object_access_hook_str;
219 : 2 : object_access_hook_str = REGRESS_object_access_hook_str;
220 : :
221 : : /* DML permission check */
222 : 2 : next_exec_check_perms_hook = ExecutorCheckPerms_hook;
223 : 2 : ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
224 : :
225 : : /* ProcessUtility hook */
226 : 2 : next_ProcessUtility_hook = ProcessUtility_hook;
227 : 2 : ProcessUtility_hook = REGRESS_utility_command;
228 : 2 : }
229 : :
230 : : static void
231 : 308 : 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 : : */
238 [ + + + - ]: 308 : if (REGRESS_audit && !IsParallelWorker())
239 : : {
240 [ + + ]: 290 : const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
241 : :
242 [ + + ]: 290 : if (objName)
243 [ + - ]: 165 : ereport(NOTICE,
244 : : (errcode(ERRCODE_INTERNAL_ERROR),
245 : : errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
246 : : else
247 [ + - ]: 125 : ereport(NOTICE,
248 : : (errcode(ERRCODE_INTERNAL_ERROR),
249 : : errmsg("in %s: %s %s %s", hook, who, type, action)));
250 : : }
251 : :
252 [ + - ]: 308 : if (action)
253 : 308 : pfree(action);
254 [ + + ]: 308 : if (objName)
255 : 173 : pfree(objName);
256 : 308 : }
257 : :
258 : : static void
259 : 160 : audit_attempt(const char *hook, char *action, char *objName)
260 : : {
261 : 160 : emit_audit_message("attempting", hook, action, objName);
262 : 160 : }
263 : :
264 : : static void
265 : 148 : audit_success(const char *hook, char *action, char *objName)
266 : : {
267 : 148 : emit_audit_message("finished", hook, action, objName);
268 : 148 : }
269 : :
270 : : static void
754 andrew@dunslane.net 271 :UBC 0 : audit_failure(const char *hook, char *action, char *objName)
272 : : {
273 : 0 : emit_audit_message("denied", hook, action, objName);
274 : 0 : }
275 : :
276 : : static void
754 andrew@dunslane.net 277 :CBC 24 : REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
278 : : {
279 : 24 : audit_attempt("object_access_hook_str",
280 : : accesstype_to_string(access, subId),
281 : : pstrdup(objName));
282 : :
283 [ - + ]: 24 : if (next_object_access_hook_str)
284 : : {
703 tgl@sss.pgh.pa.us 285 :UBC 0 : (*next_object_access_hook_str) (access, classId, objName, subId, arg);
286 : : }
287 : :
754 andrew@dunslane.net 288 [ + - ]:CBC 24 : switch (access)
289 : : {
290 : 24 : case OAT_POST_ALTER:
739 tgl@sss.pgh.pa.us 291 [ + + - + ]: 24 : if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
292 : : {
739 tgl@sss.pgh.pa.us 293 [ # # # # ]:UBC 0 : if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
294 [ # # ]: 0 : ereport(ERROR,
295 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
296 : : errmsg("permission denied: all privileges %s", objName)));
297 : : }
739 tgl@sss.pgh.pa.us 298 [ + + ]:CBC 24 : else if (subId & ACL_SET)
299 : : {
754 andrew@dunslane.net 300 [ + + + + ]: 20 : if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
301 [ + - ]: 1 : ereport(ERROR,
302 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
303 : : errmsg("permission denied: set %s", objName)));
304 : : }
305 [ + - ]: 4 : else if (subId & ACL_ALTER_SYSTEM)
306 : : {
307 [ + + - + ]: 4 : if (REGRESS_deny_alter_system && !superuser_arg(GetUserId()))
754 andrew@dunslane.net 308 [ # # ]:UBC 0 : ereport(ERROR,
309 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
310 : : errmsg("permission denied: alter system set %s", objName)));
311 : : }
312 : : else
739 tgl@sss.pgh.pa.us 313 [ # # ]: 0 : elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId);
754 andrew@dunslane.net 314 :CBC 23 : break;
754 andrew@dunslane.net 315 :UBC 0 : default:
316 : 0 : break;
317 : : }
318 : :
754 andrew@dunslane.net 319 :CBC 23 : audit_success("object_access_hook_str",
320 : : accesstype_to_string(access, subId),
321 : : pstrdup(objName));
322 : 23 : }
323 : :
324 : : static void
703 tgl@sss.pgh.pa.us 325 : 63 : REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
326 : : {
754 andrew@dunslane.net 327 : 63 : audit_attempt("object access",
328 : : accesstype_to_string(access, 0),
329 : : accesstype_arg_to_string(access, arg));
330 : :
331 [ + + - + ]: 63 : if (REGRESS_deny_object_access && !superuser_arg(GetUserId()))
754 andrew@dunslane.net 332 [ # # ]:UBC 0 : ereport(ERROR,
333 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
334 : : errmsg("permission denied: %s [%s]",
335 : : accesstype_to_string(access, 0),
336 : : accesstype_arg_to_string(access, arg))));
337 : :
338 : : /* Forward to next hook in the chain */
754 andrew@dunslane.net 339 [ - + ]:CBC 63 : if (next_object_access_hook)
703 tgl@sss.pgh.pa.us 340 :UBC 0 : (*next_object_access_hook) (access, classId, objectId, subId, arg);
341 : :
754 andrew@dunslane.net 342 :CBC 63 : audit_success("object access",
343 : : accesstype_to_string(access, 0),
344 : : accesstype_arg_to_string(access, arg));
345 : 63 : }
346 : :
347 : : static bool
495 alvherre@alvh.no-ip. 348 : 6 : REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
349 : : {
754 andrew@dunslane.net 350 : 6 : bool am_super = superuser_arg(GetUserId());
351 : 6 : bool allow = true;
352 : :
353 : 6 : audit_attempt("executor check perms", pstrdup("execute"), NULL);
354 : :
355 : : /* Perform our check */
356 [ + + + - ]: 6 : allow = !REGRESS_deny_exec_perms || am_super;
357 [ + - - + ]: 6 : if (do_abort && !allow)
754 andrew@dunslane.net 358 [ # # ]:UBC 0 : ereport(ERROR,
359 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
360 : : errmsg("permission denied: %s", "execute")));
361 : :
362 : : /* Forward to next hook in the chain */
754 andrew@dunslane.net 363 [ - + ]:CBC 6 : if (next_exec_check_perms_hook &&
495 alvherre@alvh.no-ip. 364 [ # # ]:UBC 0 : !(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
754 andrew@dunslane.net 365 : 0 : allow = false;
366 : :
754 andrew@dunslane.net 367 [ + - ]:CBC 6 : if (allow)
368 : 6 : audit_success("executor check perms",
369 : : pstrdup("execute"),
370 : : NULL);
371 : : else
754 andrew@dunslane.net 372 :UBC 0 : audit_failure("executor check perms",
373 : : pstrdup("execute"),
374 : : NULL);
375 : :
754 andrew@dunslane.net 376 :CBC 6 : return allow;
377 : : }
378 : :
379 : : static void
380 : 67 : REGRESS_utility_command(PlannedStmt *pstmt,
381 : : const char *queryString,
382 : : bool readOnlyTree,
383 : : ProcessUtilityContext context,
384 : : ParamListInfo params,
385 : : QueryEnvironment *queryEnv,
386 : : DestReceiver *dest,
387 : : QueryCompletion *qc)
388 : : {
389 : 67 : Node *parsetree = pstmt->utilityStmt;
623 tgl@sss.pgh.pa.us 390 : 67 : const char *action = GetCommandTagName(CreateCommandTag(parsetree));
391 : :
754 andrew@dunslane.net 392 : 67 : audit_attempt("process utility",
393 : : pstrdup(action),
394 : : NULL);
395 : :
396 : : /* Check permissions */
397 [ + + - + ]: 67 : if (REGRESS_deny_utility_commands && !superuser_arg(GetUserId()))
754 andrew@dunslane.net 398 [ # # ]:UBC 0 : ereport(ERROR,
399 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
400 : : errmsg("permission denied: %s", action)));
401 : :
402 : : /* Forward to next hook in the chain */
754 andrew@dunslane.net 403 [ - + ]:CBC 67 : if (next_ProcessUtility_hook)
754 andrew@dunslane.net 404 :UBC 0 : (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
405 : : context, params, queryEnv,
406 : : dest, qc);
407 : : else
754 andrew@dunslane.net 408 :CBC 67 : standard_ProcessUtility(pstmt, queryString, readOnlyTree,
409 : : context, params, queryEnv,
410 : : dest, qc);
411 : :
412 : : /* We're done */
413 : 56 : audit_success("process utility",
414 : : pstrdup(action),
415 : : NULL);
416 : 56 : }
417 : :
418 : : static char *
419 : 173 : accesstype_to_string(ObjectAccessType access, int subId)
420 : : {
421 : : const char *type;
422 : :
423 [ + + + + : 173 : switch (access)
- - - ]
424 : : {
425 : 34 : case OAT_POST_CREATE:
426 : 34 : type = "create";
427 : 34 : break;
428 : 24 : case OAT_DROP:
429 : 24 : type = "drop";
430 : 24 : break;
431 : 71 : case OAT_POST_ALTER:
432 : 71 : type = "alter";
433 : 71 : break;
434 : 44 : case OAT_NAMESPACE_SEARCH:
435 : 44 : type = "namespace search";
436 : 44 : break;
754 andrew@dunslane.net 437 :UBC 0 : case OAT_FUNCTION_EXECUTE:
438 : 0 : type = "execute";
439 : 0 : break;
440 : 0 : case OAT_TRUNCATE:
441 : 0 : type = "truncate";
442 : 0 : break;
443 : 0 : default:
444 : 0 : type = "UNRECOGNIZED ObjectAccessType";
445 : : }
446 : :
739 tgl@sss.pgh.pa.us 447 [ + + - + ]:CBC 173 : if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
739 tgl@sss.pgh.pa.us 448 :UBC 0 : return psprintf("%s (subId=0x%x, all privileges)", type, subId);
739 tgl@sss.pgh.pa.us 449 [ + + ]:CBC 173 : if (subId & ACL_SET)
450 : 39 : return psprintf("%s (subId=0x%x, set)", type, subId);
754 andrew@dunslane.net 451 [ + + ]: 134 : if (subId & ACL_ALTER_SYSTEM)
739 tgl@sss.pgh.pa.us 452 : 8 : return psprintf("%s (subId=0x%x, alter system)", type, subId);
453 : :
454 : 126 : return psprintf("%s (subId=0x%x)", type, subId);
455 : : }
456 : :
457 : : static char *
754 andrew@dunslane.net 458 : 126 : accesstype_arg_to_string(ObjectAccessType access, void *arg)
459 : : {
460 [ - + ]: 126 : if (arg == NULL)
754 andrew@dunslane.net 461 :UBC 0 : return pstrdup("extra info null");
462 : :
754 andrew@dunslane.net 463 [ + + + + :CBC 126 : switch (access)
- - ]
464 : : {
465 : 34 : case OAT_POST_CREATE:
466 : : {
703 tgl@sss.pgh.pa.us 467 : 34 : ObjectAccessPostCreate *pc_arg = (ObjectAccessPostCreate *) arg;
468 : :
754 andrew@dunslane.net 469 [ + + ]: 34 : return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
470 : : }
471 : : break;
472 : 24 : case OAT_DROP:
473 : : {
703 tgl@sss.pgh.pa.us 474 : 24 : ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
475 : :
754 andrew@dunslane.net 476 :UBC 0 : return psprintf("%s%s%s%s%s%s",
703 tgl@sss.pgh.pa.us 477 [ - + ]:CBC 24 : ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
478 : : ? "internal action," : ""),
641 alvherre@alvh.no-ip. 479 [ + + ]: 24 : ((drop_arg->dropflags & PERFORM_DELETION_CONCURRENTLY)
480 : : ? "concurrent drop," : ""),
481 [ - + ]: 24 : ((drop_arg->dropflags & PERFORM_DELETION_QUIETLY)
482 : : ? "suppress notices," : ""),
483 [ - + ]: 24 : ((drop_arg->dropflags & PERFORM_DELETION_SKIP_ORIGINAL)
484 : : ? "keep original object," : ""),
485 [ - + ]: 24 : ((drop_arg->dropflags & PERFORM_DELETION_SKIP_EXTENSIONS)
486 : : ? "keep extensions," : ""),
487 [ - + ]: 24 : ((drop_arg->dropflags & PERFORM_DELETION_CONCURRENT_LOCK)
488 : : ? "normal concurrent drop," : ""));
489 : : }
490 : : break;
754 andrew@dunslane.net 491 :GBC 24 : case OAT_POST_ALTER:
492 : : {
703 tgl@sss.pgh.pa.us 493 : 24 : ObjectAccessPostAlter *pa_arg = (ObjectAccessPostAlter *) arg;
494 : :
754 andrew@dunslane.net 495 :UBC 0 : return psprintf("%s %s auxiliary object",
703 tgl@sss.pgh.pa.us 496 [ - + ]:GBC 24 : (pa_arg->is_internal ? "internal" : "explicit"),
497 [ - + ]: 24 : (OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
498 : : }
499 : : break;
754 andrew@dunslane.net 500 :CBC 44 : case OAT_NAMESPACE_SEARCH:
501 : : {
703 tgl@sss.pgh.pa.us 502 : 44 : ObjectAccessNamespaceSearch *ns_arg = (ObjectAccessNamespaceSearch *) arg;
503 : :
754 andrew@dunslane.net 504 :UBC 0 : return psprintf("%s, %s",
703 tgl@sss.pgh.pa.us 505 [ + + ]:CBC 44 : (ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
506 [ + - ]: 44 : (ns_arg->result ? "allowed" : "denied"));
507 : : }
508 : : break;
754 andrew@dunslane.net 509 :UBC 0 : case OAT_TRUNCATE:
510 : : case OAT_FUNCTION_EXECUTE:
511 : : /* hook takes no arg. */
512 : 0 : return pstrdup("unexpected extra info pointer received");
513 : 0 : default:
514 : 0 : return pstrdup("cannot parse extra info for unrecognized access type");
515 : : }
516 : :
517 : : return pstrdup("unknown");
518 : : }
|