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