Age Owner 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-2023, 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 :
383 andrew 25 CBC 1 : 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
383 andrew 73 ECB : */
74 : void
383 andrew 75 GIC 1 : _PG_init(void)
76 : {
77 : /*
383 andrew 78 ECB : * test_oat_hooks.deny_set_variable = (on|off)
79 : */
383 andrew 80 GIC 1 : 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 : /*
383 andrew 92 ECB : * test_oat_hooks.deny_alter_system = (on|off)
93 : */
383 andrew 94 GIC 1 : 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 : /*
383 andrew 106 ECB : * test_oat_hooks.deny_object_access = (on|off)
107 : */
383 andrew 108 GIC 1 : 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 : /*
383 andrew 120 ECB : * test_oat_hooks.deny_exec_perms = (on|off)
121 : */
383 andrew 122 GIC 1 : 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 : /*
383 andrew 134 ECB : * test_oat_hooks.deny_utility_commands = (on|off)
135 : */
383 andrew 136 GIC 1 : 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 : /*
383 andrew 148 ECB : * test_oat_hooks.audit = (on|off)
149 : */
383 andrew 150 GIC 1 : 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 : /*
368 tgl 162 ECB : * test_oat_hooks.user_var{1,2} = (on|off)
163 : */
368 tgl 164 GIC 1 : 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,
368 tgl 173 ECB : NULL);
174 :
368 tgl 175 GIC 1 : 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 : /*
368 tgl 187 ECB : * test_oat_hooks.super_var{1,2} = (on|off)
188 : */
368 tgl 189 GIC 1 : 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,
368 tgl 198 ECB : NULL);
199 :
368 tgl 200 GIC 1 : 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,
368 tgl 209 ECB : NULL);
210 :
383 andrew 211 GIC 1 : MarkGUCPrefixReserved("test_oat_hooks");
383 andrew 212 ECB :
213 : /* Object access hook */
383 andrew 214 GIC 1 : next_object_access_hook = object_access_hook;
215 1 : object_access_hook = REGRESS_object_access_hook;
383 andrew 216 ECB :
217 : /* Object access hook str */
383 andrew 218 GIC 1 : next_object_access_hook_str = object_access_hook_str;
219 1 : object_access_hook_str = REGRESS_object_access_hook_str;
383 andrew 220 ECB :
221 : /* DML permission check */
383 andrew 222 GIC 1 : next_exec_check_perms_hook = ExecutorCheckPerms_hook;
223 1 : ExecutorCheckPerms_hook = REGRESS_exec_check_perms;
383 andrew 224 ECB :
225 : /* ProcessUtility hook */
383 andrew 226 CBC 1 : next_ProcessUtility_hook = ProcessUtility_hook;
383 andrew 227 GIC 1 : ProcessUtility_hook = REGRESS_utility_command;
228 1 : }
383 andrew 229 ECB :
230 : static void
383 andrew 231 GIC 182 : 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 : */
383 andrew 238 CBC 182 : if (REGRESS_audit && !IsParallelWorker())
239 : {
240 165 : const char *who = superuser_arg(GetUserId()) ? "superuser" : "non-superuser";
383 andrew 241 ECB :
383 andrew 242 GIC 165 : if (objName)
243 69 : ereport(NOTICE,
244 : (errcode(ERRCODE_INTERNAL_ERROR),
383 andrew 245 ECB : errmsg("in %s: %s %s %s [%s]", hook, who, type, action, objName)));
246 : else
383 andrew 247 GIC 96 : ereport(NOTICE,
248 : (errcode(ERRCODE_INTERNAL_ERROR),
249 : errmsg("in %s: %s %s %s", hook, who, type, action)));
383 andrew 250 ECB : }
251 :
383 andrew 252 CBC 182 : if (action)
253 182 : pfree(action);
254 182 : if (objName)
383 andrew 255 GIC 77 : pfree(objName);
256 182 : }
383 andrew 257 ECB :
258 : static void
383 andrew 259 CBC 97 : audit_attempt(const char *hook, char *action, char *objName)
383 andrew 260 ECB : {
383 andrew 261 GIC 97 : emit_audit_message("attempting", hook, action, objName);
262 97 : }
383 andrew 263 ECB :
264 : static void
383 andrew 265 CBC 85 : audit_success(const char *hook, char *action, char *objName)
383 andrew 266 ECB : {
383 andrew 267 GIC 85 : emit_audit_message("finished", hook, action, objName);
268 85 : }
383 andrew 269 EUB :
270 : static void
383 andrew 271 UBC 0 : audit_failure(const char *hook, char *action, char *objName)
383 andrew 272 EUB : {
383 andrew 273 UIC 0 : emit_audit_message("denied", hook, action, objName);
274 0 : }
383 andrew 275 ECB :
276 : static void
383 andrew 277 CBC 23 : REGRESS_object_access_hook_str(ObjectAccessType access, Oid classId, const char *objName, int subId, void *arg)
278 : {
383 andrew 279 GIC 23 : audit_attempt("object_access_hook_str",
280 : accesstype_to_string(access, subId),
383 andrew 281 ECB : pstrdup(objName));
282 :
383 andrew 283 GBC 23 : if (next_object_access_hook_str)
284 : {
332 tgl 285 UIC 0 : (*next_object_access_hook_str) (access, classId, objName, subId, arg);
383 andrew 286 ECB : }
287 :
383 andrew 288 CBC 23 : switch (access)
383 andrew 289 ECB : {
383 andrew 290 GIC 23 : case OAT_POST_ALTER:
368 tgl 291 GBC 23 : if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
368 tgl 292 EUB : {
368 tgl 293 UIC 0 : if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
294 0 : ereport(ERROR,
295 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
368 tgl 296 ECB : errmsg("permission denied: all privileges %s", objName)));
297 : }
368 tgl 298 CBC 23 : else if (subId & ACL_SET)
383 andrew 299 ECB : {
383 andrew 300 GIC 19 : if (REGRESS_deny_set_variable && !superuser_arg(GetUserId()))
301 1 : ereport(ERROR,
302 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
383 andrew 303 ECB : errmsg("permission denied: set %s", objName)));
304 : }
383 andrew 305 CBC 4 : else if (subId & ACL_ALTER_SYSTEM)
383 andrew 306 EUB : {
383 andrew 307 GIC 4 : if (REGRESS_deny_alter_system && !superuser_arg(GetUserId()))
383 andrew 308 UIC 0 : ereport(ERROR,
309 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
310 : errmsg("permission denied: alter system set %s", objName)));
383 andrew 311 EUB : }
383 andrew 312 ECB : else
368 tgl 313 UBC 0 : elog(ERROR, "Unknown ParameterAclRelationId subId: %d", subId);
383 andrew 314 GBC 22 : break;
383 andrew 315 UIC 0 : default:
316 0 : break;
383 andrew 317 ECB : }
318 :
383 andrew 319 GIC 22 : audit_success("object_access_hook_str",
383 andrew 320 ECB : accesstype_to_string(access, subId),
321 : pstrdup(objName));
383 andrew 322 GIC 22 : }
383 andrew 323 ECB :
324 : static void
332 tgl 325 CBC 16 : REGRESS_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId, int subId, void *arg)
326 : {
383 andrew 327 GIC 16 : audit_attempt("object access",
328 : accesstype_to_string(access, 0),
383 andrew 329 ECB : accesstype_arg_to_string(access, arg));
383 andrew 330 EUB :
383 andrew 331 GIC 16 : if (REGRESS_deny_object_access && !superuser_arg(GetUserId()))
383 andrew 332 UIC 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))));
383 andrew 337 ECB :
383 andrew 338 EUB : /* Forward to next hook in the chain */
383 andrew 339 GIC 16 : if (next_object_access_hook)
332 tgl 340 LBC 0 : (*next_object_access_hook) (access, classId, objectId, subId, arg);
341 :
383 andrew 342 GIC 16 : audit_success("object access",
383 andrew 343 ECB : accesstype_to_string(access, 0),
344 : accesstype_arg_to_string(access, arg));
383 andrew 345 GIC 16 : }
383 andrew 346 ECB :
347 : static bool
124 alvherre 348 GNC 6 : REGRESS_exec_check_perms(List *rangeTabls, List *rteperminfos, bool do_abort)
383 andrew 349 ECB : {
383 andrew 350 GIC 6 : bool am_super = superuser_arg(GetUserId());
383 andrew 351 CBC 6 : bool allow = true;
352 :
383 andrew 353 GIC 6 : audit_attempt("executor check perms", pstrdup("execute"), NULL);
383 andrew 354 ECB :
355 : /* Perform our check */
383 andrew 356 GBC 6 : allow = !REGRESS_deny_exec_perms || am_super;
383 andrew 357 GIC 6 : if (do_abort && !allow)
383 andrew 358 UIC 0 : ereport(ERROR,
359 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
360 : errmsg("permission denied: %s", "execute")));
383 andrew 361 ECB :
383 andrew 362 EUB : /* Forward to next hook in the chain */
383 andrew 363 GBC 6 : if (next_exec_check_perms_hook &&
124 alvherre 364 UNC 0 : !(*next_exec_check_perms_hook) (rangeTabls, rteperminfos, do_abort))
383 andrew 365 LBC 0 : allow = false;
383 andrew 366 ECB :
383 andrew 367 GIC 6 : if (allow)
368 6 : audit_success("executor check perms",
369 : pstrdup("execute"),
383 andrew 370 EUB : NULL);
371 : else
383 andrew 372 UIC 0 : audit_failure("executor check perms",
373 : pstrdup("execute"),
383 andrew 374 ECB : NULL);
375 :
383 andrew 376 GIC 6 : return allow;
377 : }
383 andrew 378 ECB :
379 : static void
383 andrew 380 GIC 52 : REGRESS_utility_command(PlannedStmt *pstmt,
381 : const char *queryString,
382 : bool readOnlyTree,
383 : ProcessUtilityContext context,
384 : ParamListInfo params,
385 : QueryEnvironment *queryEnv,
386 : DestReceiver *dest,
332 tgl 387 ECB : QueryCompletion *qc)
383 andrew 388 : {
383 andrew 389 GIC 52 : Node *parsetree = pstmt->utilityStmt;
252 tgl 390 CBC 52 : const char *action = GetCommandTagName(CreateCommandTag(parsetree));
391 :
383 andrew 392 GIC 52 : audit_attempt("process utility",
393 : pstrdup(action),
394 : NULL);
383 andrew 395 ECB :
383 andrew 396 EUB : /* Check permissions */
383 andrew 397 GIC 52 : if (REGRESS_deny_utility_commands && !superuser_arg(GetUserId()))
383 andrew 398 UIC 0 : ereport(ERROR,
399 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
400 : errmsg("permission denied: %s", action)));
383 andrew 401 ECB :
383 andrew 402 EUB : /* Forward to next hook in the chain */
383 andrew 403 GIC 52 : if (next_ProcessUtility_hook)
383 andrew 404 UIC 0 : (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
405 : context, params, queryEnv,
383 andrew 406 ECB : dest, qc);
407 : else
383 andrew 408 GIC 52 : standard_ProcessUtility(pstmt, queryString, readOnlyTree,
409 : context, params, queryEnv,
410 : dest, qc);
383 andrew 411 ECB :
412 : /* We're done */
383 andrew 413 GIC 41 : audit_success("process utility",
383 andrew 414 ECB : pstrdup(action),
415 : NULL);
383 andrew 416 GIC 41 : }
383 andrew 417 ECB :
418 : static char *
383 andrew 419 GIC 77 : accesstype_to_string(ObjectAccessType access, int subId)
420 : {
383 andrew 421 ECB : const char *type;
422 :
383 andrew 423 CBC 77 : switch (access)
383 andrew 424 ECB : {
383 andrew 425 CBC 16 : case OAT_POST_CREATE:
426 16 : type = "create";
427 16 : break;
428 10 : case OAT_DROP:
429 10 : type = "drop";
430 10 : break;
431 45 : case OAT_POST_ALTER:
432 45 : type = "alter";
433 45 : break;
434 6 : case OAT_NAMESPACE_SEARCH:
383 andrew 435 GBC 6 : type = "namespace search";
436 6 : break;
383 andrew 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;
383 andrew 443 UIC 0 : default:
444 0 : type = "UNRECOGNIZED ObjectAccessType";
383 andrew 445 ECB : }
383 andrew 446 EUB :
368 tgl 447 CBC 77 : if ((subId & ACL_SET) && (subId & ACL_ALTER_SYSTEM))
368 tgl 448 LBC 0 : return psprintf("%s (subId=0x%x, all privileges)", type, subId);
368 tgl 449 CBC 77 : if (subId & ACL_SET)
450 37 : return psprintf("%s (subId=0x%x, set)", type, subId);
383 andrew 451 GIC 40 : if (subId & ACL_ALTER_SYSTEM)
368 tgl 452 CBC 8 : return psprintf("%s (subId=0x%x, alter system)", type, subId);
453 :
368 tgl 454 GIC 32 : return psprintf("%s (subId=0x%x)", type, subId);
455 : }
383 andrew 456 ECB :
457 : static char *
383 andrew 458 CBC 32 : accesstype_arg_to_string(ObjectAccessType access, void *arg)
383 andrew 459 EUB : {
383 andrew 460 GIC 32 : if (arg == NULL)
383 andrew 461 LBC 0 : return pstrdup("extra info null");
462 :
383 andrew 463 CBC 32 : switch (access)
464 : {
465 16 : case OAT_POST_CREATE:
466 : {
332 tgl 467 16 : ObjectAccessPostCreate *pc_arg = (ObjectAccessPostCreate *) arg;
468 :
383 andrew 469 GIC 16 : return pstrdup(pc_arg->is_internal ? "internal" : "explicit");
383 andrew 470 ECB : }
471 : break;
383 andrew 472 CBC 10 : case OAT_DROP:
473 : {
332 tgl 474 10 : ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
383 andrew 475 ECB :
383 andrew 476 GIC 60 : return psprintf("%s%s%s%s%s%s",
332 tgl 477 CBC 10 : ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL)
478 : ? "internal action," : ""),
270 alvherre 479 GNC 10 : ((drop_arg->dropflags & PERFORM_DELETION_CONCURRENTLY)
480 : ? "concurrent drop," : ""),
481 10 : ((drop_arg->dropflags & PERFORM_DELETION_QUIETLY)
482 : ? "suppress notices," : ""),
483 10 : ((drop_arg->dropflags & PERFORM_DELETION_SKIP_ORIGINAL)
484 : ? "keep original object," : ""),
485 10 : ((drop_arg->dropflags & PERFORM_DELETION_SKIP_EXTENSIONS)
486 : ? "keep extensions," : ""),
487 10 : ((drop_arg->dropflags & PERFORM_DELETION_CONCURRENT_LOCK)
488 : ? "normal concurrent drop," : ""));
383 andrew 489 EUB : }
490 : break;
383 andrew 491 UBC 0 : case OAT_POST_ALTER:
492 : {
332 tgl 493 0 : ObjectAccessPostAlter *pa_arg = (ObjectAccessPostAlter *) arg;
383 andrew 494 EUB :
383 andrew 495 UBC 0 : return psprintf("%s %s auxiliary object",
332 tgl 496 UIC 0 : (pa_arg->is_internal ? "internal" : "explicit"),
497 0 : (OidIsValid(pa_arg->auxiliary_id) ? "with" : "without"));
383 andrew 498 ECB : }
499 : break;
383 andrew 500 CBC 6 : case OAT_NAMESPACE_SEARCH:
501 : {
332 tgl 502 6 : ObjectAccessNamespaceSearch *ns_arg = (ObjectAccessNamespaceSearch *) arg;
383 andrew 503 ECB :
383 andrew 504 CBC 12 : return psprintf("%s, %s",
332 tgl 505 GIC 6 : (ns_arg->ereport_on_violation ? "report on violation" : "no report on violation"),
506 6 : (ns_arg->result ? "allowed" : "denied"));
383 andrew 507 EUB : }
508 : break;
383 andrew 509 UIC 0 : case OAT_TRUNCATE:
383 andrew 510 EUB : case OAT_FUNCTION_EXECUTE:
511 : /* hook takes no arg. */
383 andrew 512 UBC 0 : return pstrdup("unexpected extra info pointer received");
383 andrew 513 UIC 0 : default:
514 0 : return pstrdup("cannot parse extra info for unrecognized access type");
515 : }
516 :
517 : return pstrdup("unknown");
518 : }
|