Age Owner Branch data TLA Line data Source code
1 : : /* -------------------------------------------------------------------------
2 : : *
3 : : * contrib/sepgsql/hooks.c
4 : : *
5 : : * Entrypoints of the hooks in PostgreSQL, and dispatches the callbacks.
6 : : *
7 : : * Copyright (c) 2010-2025, PostgreSQL Global Development Group
8 : : *
9 : : * -------------------------------------------------------------------------
10 : : */
11 : : #include "postgres.h"
12 : :
13 : : #include "catalog/dependency.h"
14 : : #include "catalog/objectaccess.h"
15 : : #include "catalog/pg_class.h"
16 : : #include "catalog/pg_database.h"
17 : : #include "catalog/pg_namespace.h"
18 : : #include "catalog/pg_proc.h"
19 : : #include "commands/seclabel.h"
20 : : #include "executor/executor.h"
21 : : #include "fmgr.h"
22 : : #include "miscadmin.h"
23 : : #include "sepgsql.h"
24 : : #include "tcop/utility.h"
25 : : #include "utils/guc.h"
26 : : #include "utils/queryenvironment.h"
27 : :
164 tgl@sss.pgh.pa.us 28 :UBC 0 : PG_MODULE_MAGIC_EXT(
29 : : .name = "sepgsql",
30 : : .version = PG_VERSION
31 : : );
32 : :
33 : : /*
34 : : * Declarations
35 : : */
36 : :
37 : : /*
38 : : * Saved hook entries (if stacked)
39 : : */
40 : : static object_access_hook_type next_object_access_hook = NULL;
41 : : static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
42 : : static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
43 : :
44 : : /*
45 : : * Contextual information on DDL commands
46 : : */
47 : : typedef struct
48 : : {
49 : : NodeTag cmdtype;
50 : :
51 : : /*
52 : : * Name of the template database given by users on CREATE DATABASE
53 : : * command. Elsewhere (including the case of default) NULL.
54 : : */
55 : : const char *createdb_dtemplate;
56 : : } sepgsql_context_info_t;
57 : :
58 : : static sepgsql_context_info_t sepgsql_context_info;
59 : :
60 : : /*
61 : : * GUC: sepgsql.permissive = (on|off)
62 : : */
63 : : static bool sepgsql_permissive = false;
64 : :
65 : : bool
5340 rhaas@postgresql.org 66 : 0 : sepgsql_get_permissive(void)
67 : : {
68 : 0 : return sepgsql_permissive;
69 : : }
70 : :
71 : : /*
72 : : * GUC: sepgsql.debug_audit = (on|off)
73 : : */
74 : : static bool sepgsql_debug_audit = false;
75 : :
76 : : bool
77 : 0 : sepgsql_get_debug_audit(void)
78 : : {
79 : 0 : return sepgsql_debug_audit;
80 : : }
81 : :
82 : : /*
83 : : * sepgsql_object_access
84 : : *
85 : : * Entrypoint of the object_access_hook. This routine performs as
86 : : * a dispatcher of invocation based on access type and object classes.
87 : : */
88 : : static void
89 : 0 : sepgsql_object_access(ObjectAccessType access,
90 : : Oid classId,
91 : : Oid objectId,
92 : : int subId,
93 : : void *arg)
94 : : {
95 [ # # ]: 0 : if (next_object_access_hook)
4929 96 : 0 : (*next_object_access_hook) (access, classId, objectId, subId, arg);
97 : :
5340 98 [ # # # # : 0 : switch (access)
# # # ]
99 : : {
100 : 0 : case OAT_POST_CREATE:
101 : : {
4701 alvherre@alvh.no-ip. 102 : 0 : ObjectAccessPostCreate *pc_arg = arg;
103 : : bool is_internal;
104 : :
105 [ # # # # ]: 0 : is_internal = pc_arg ? pc_arg->is_internal : false;
106 : :
107 [ # # # # : 0 : switch (classId)
# ]
108 : : {
109 : 0 : case DatabaseRelationId:
110 [ # # ]: 0 : Assert(!is_internal);
111 : 0 : sepgsql_database_post_create(objectId,
112 : : sepgsql_context_info.createdb_dtemplate);
113 : 0 : break;
114 : :
115 : 0 : case NamespaceRelationId:
116 [ # # ]: 0 : Assert(!is_internal);
117 : 0 : sepgsql_schema_post_create(objectId);
118 : 0 : break;
119 : :
120 : 0 : case RelationRelationId:
121 [ # # ]: 0 : if (subId == 0)
122 : : {
123 : : /*
124 : : * The cases in which we want to apply permission
125 : : * checks on creation of a new relation correspond
126 : : * to direct user invocation. For internal uses,
127 : : * that is creation of toast tables, index rebuild
128 : : * or ALTER TABLE commands, we need neither
129 : : * assignment of security labels nor permission
130 : : * checks.
131 : : */
132 [ # # ]: 0 : if (is_internal)
5008 rhaas@postgresql.org 133 : 0 : break;
134 : :
4701 alvherre@alvh.no-ip. 135 : 0 : sepgsql_relation_post_create(objectId);
136 : : }
137 : : else
138 : 0 : sepgsql_attribute_post_create(objectId, subId);
139 : 0 : break;
140 : :
141 : 0 : case ProcedureRelationId:
142 [ # # ]: 0 : Assert(!is_internal);
143 : 0 : sepgsql_proc_post_create(objectId);
144 : 0 : break;
145 : :
146 : 0 : default:
147 : : /* Ignore unsupported object classes */
148 : 0 : break;
149 : : }
150 : : }
5340 rhaas@postgresql.org 151 : 0 : break;
152 : :
4929 153 : 0 : case OAT_DROP:
154 : : {
4836 bruce@momjian.us 155 : 0 : ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
156 : :
157 : : /*
158 : : * No need to apply permission checks on object deletion due
159 : : * to internal cleanups; such as removal of temporary database
160 : : * object on session closed.
161 : : */
4929 rhaas@postgresql.org 162 [ # # ]: 0 : if ((drop_arg->dropflags & PERFORM_DELETION_INTERNAL) != 0)
163 : 0 : break;
164 : :
165 [ # # # # : 0 : switch (classId)
# ]
166 : : {
167 : 0 : case DatabaseRelationId:
168 : 0 : sepgsql_database_drop(objectId);
169 : 0 : break;
170 : :
171 : 0 : case NamespaceRelationId:
172 : 0 : sepgsql_schema_drop(objectId);
173 : 0 : break;
174 : :
175 : 0 : case RelationRelationId:
176 [ # # ]: 0 : if (subId == 0)
177 : 0 : sepgsql_relation_drop(objectId);
178 : : else
179 : 0 : sepgsql_attribute_drop(objectId, subId);
180 : 0 : break;
181 : :
182 : 0 : case ProcedureRelationId:
183 : 0 : sepgsql_proc_drop(objectId);
184 : 0 : break;
185 : :
4546 186 : 0 : default:
187 : : /* Ignore unsupported object classes */
188 : 0 : break;
189 : : }
190 : : }
191 : 0 : break;
192 : :
2114 mail@joeconway.com 193 : 0 : case OAT_TRUNCATE:
194 : : {
195 [ # # ]: 0 : switch (classId)
196 : : {
197 : 0 : case RelationRelationId:
198 : 0 : sepgsql_relation_truncate(objectId);
199 : 0 : break;
200 : 0 : default:
201 : : /* Ignore unsupported object classes */
202 : 0 : break;
203 : : }
204 : : }
205 : 0 : break;
206 : :
4546 rhaas@postgresql.org 207 : 0 : case OAT_POST_ALTER:
208 : : {
4483 bruce@momjian.us 209 : 0 : ObjectAccessPostAlter *pa_arg = arg;
210 : 0 : bool is_internal = pa_arg->is_internal;
211 : :
4546 rhaas@postgresql.org 212 [ # # # # : 0 : switch (classId)
# ]
213 : : {
214 : 0 : case DatabaseRelationId:
215 [ # # ]: 0 : Assert(!is_internal);
216 : 0 : sepgsql_database_setattr(objectId);
217 : 0 : break;
218 : :
219 : 0 : case NamespaceRelationId:
220 [ # # ]: 0 : Assert(!is_internal);
221 : 0 : sepgsql_schema_setattr(objectId);
222 : 0 : break;
223 : :
224 : 0 : case RelationRelationId:
225 [ # # ]: 0 : if (subId == 0)
226 : : {
227 : : /*
228 : : * A case when we don't want to apply permission
229 : : * check is that relation is internally altered
230 : : * without user's intention. E.g, no need to check
231 : : * on toast table/index to be renamed at end of
232 : : * the table rewrites.
233 : : */
234 [ # # ]: 0 : if (is_internal)
4483 bruce@momjian.us 235 : 0 : break;
236 : :
4546 rhaas@postgresql.org 237 : 0 : sepgsql_relation_setattr(objectId);
238 : : }
239 : : else
4483 bruce@momjian.us 240 : 0 : sepgsql_attribute_setattr(objectId, subId);
4546 rhaas@postgresql.org 241 : 0 : break;
242 : :
243 : 0 : case ProcedureRelationId:
244 [ # # ]: 0 : Assert(!is_internal);
245 : 0 : sepgsql_proc_setattr(objectId);
246 : 0 : break;
247 : :
4929 248 : 0 : default:
249 : : /* Ignore unsupported object classes */
250 : 0 : break;
251 : : }
252 : : }
253 : 0 : break;
254 : :
4537 255 : 0 : case OAT_NAMESPACE_SEARCH:
256 : : {
4483 bruce@momjian.us 257 : 0 : ObjectAccessNamespaceSearch *ns_arg = arg;
258 : :
259 : : /*
260 : : * If stacked extension already decided not to allow users to
261 : : * search this schema, we just stick with that decision.
262 : : */
4537 rhaas@postgresql.org 263 [ # # ]: 0 : if (!ns_arg->result)
264 : 0 : break;
265 : :
266 [ # # ]: 0 : Assert(classId == NamespaceRelationId);
267 [ # # ]: 0 : Assert(ns_arg->result);
268 : : ns_arg->result
269 : 0 : = sepgsql_schema_search(objectId,
270 : 0 : ns_arg->ereport_on_violation);
271 : : }
272 : 0 : break;
273 : :
4530 274 : 0 : case OAT_FUNCTION_EXECUTE:
275 : : {
276 [ # # ]: 0 : Assert(classId == ProcedureRelationId);
277 : 0 : sepgsql_proc_execute(objectId);
278 : : }
279 : 0 : break;
280 : :
5340 281 : 0 : default:
5263 bruce@momjian.us 282 [ # # ]: 0 : elog(ERROR, "unexpected object access type: %d", (int) access);
283 : : break;
284 : : }
5340 rhaas@postgresql.org 285 : 0 : }
286 : :
287 : : /*
288 : : * sepgsql_exec_check_perms
289 : : *
290 : : * Entrypoint of DML permissions
291 : : */
292 : : static bool
1005 alvherre@alvh.no-ip. 293 : 0 : sepgsql_exec_check_perms(List *rangeTbls, List *rteperminfos, bool abort)
294 : : {
295 : : /*
296 : : * If security provider is stacking and one of them replied 'false' at
297 : : * least, we don't need to check any more.
298 : : */
5340 rhaas@postgresql.org 299 [ # # ]: 0 : if (next_exec_check_perms_hook &&
1005 alvherre@alvh.no-ip. 300 [ # # ]: 0 : !(*next_exec_check_perms_hook) (rangeTbls, rteperminfos, abort))
5340 rhaas@postgresql.org 301 : 0 : return false;
302 : :
1005 alvherre@alvh.no-ip. 303 [ # # ]: 0 : if (!sepgsql_dml_privileges(rangeTbls, rteperminfos, abort))
5340 rhaas@postgresql.org 304 : 0 : return false;
305 : :
306 : 0 : return true;
307 : : }
308 : :
309 : : /*
310 : : * sepgsql_utility_command
311 : : *
312 : : * It tries to rough-grained control on utility commands; some of them can
313 : : * break whole of the things if nefarious user would use.
314 : : */
315 : : static void
3157 tgl@sss.pgh.pa.us 316 : 0 : sepgsql_utility_command(PlannedStmt *pstmt,
317 : : const char *queryString,
318 : : bool readOnlyTree,
319 : : ProcessUtilityContext context,
320 : : ParamListInfo params,
321 : : QueryEnvironment *queryEnv,
322 : : DestReceiver *dest,
323 : : QueryCompletion *qc)
324 : : {
325 : 0 : Node *parsetree = pstmt->utilityStmt;
4836 bruce@momjian.us 326 : 0 : sepgsql_context_info_t saved_context_info = sepgsql_context_info;
327 : : ListCell *cell;
328 : :
5008 rhaas@postgresql.org 329 [ # # ]: 0 : PG_TRY();
330 : : {
331 : : /*
332 : : * Check command tag to avoid nefarious operations, and save the
333 : : * current contextual information to determine whether we should apply
334 : : * permission checks here, or not.
335 : : */
336 : 0 : sepgsql_context_info.cmdtype = nodeTag(parsetree);
337 : :
338 [ # # # ]: 0 : switch (nodeTag(parsetree))
339 : : {
340 : 0 : case T_CreatedbStmt:
341 : :
342 : : /*
343 : : * We hope to reference name of the source database, but it
344 : : * does not appear in system catalog. So, we save it here.
345 : : */
4836 bruce@momjian.us 346 [ # # # # : 0 : foreach(cell, ((CreatedbStmt *) parsetree)->options)
# # ]
347 : : {
348 : 0 : DefElem *defel = (DefElem *) lfirst(cell);
349 : :
5008 rhaas@postgresql.org 350 [ # # ]: 0 : if (strcmp(defel->defname, "template") == 0)
351 : : {
352 : : sepgsql_context_info.createdb_dtemplate
353 : 0 : = strVal(defel->arg);
354 : 0 : break;
355 : : }
356 : : }
357 : 0 : break;
358 : :
359 : 0 : case T_LoadStmt:
360 : :
361 : : /*
362 : : * We reject LOAD command across the board on enforcing mode,
363 : : * because a binary module can arbitrarily override hooks.
364 : : */
365 [ # # ]: 0 : if (sepgsql_getenforce())
366 : : {
367 [ # # ]: 0 : ereport(ERROR,
368 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
369 : : errmsg("SELinux: LOAD is not permitted")));
370 : : }
371 : 0 : break;
372 : 0 : default:
373 : :
374 : : /*
375 : : * Right now we don't check any other utility commands,
376 : : * because it needs more detailed information to make access
377 : : * control decision here, but we don't want to have two parse
378 : : * and analyze routines individually.
379 : : */
380 : 0 : break;
381 : : }
382 : :
383 [ # # ]: 0 : if (next_ProcessUtility_hook)
1541 tgl@sss.pgh.pa.us 384 : 0 : (*next_ProcessUtility_hook) (pstmt, queryString, readOnlyTree,
385 : : context, params, queryEnv,
386 : : dest, qc);
387 : : else
388 : 0 : standard_ProcessUtility(pstmt, queryString, readOnlyTree,
389 : : context, params, queryEnv,
390 : : dest, qc);
391 : : }
2136 peter@eisentraut.org 392 : 0 : PG_FINALLY();
393 : : {
5008 rhaas@postgresql.org 394 : 0 : sepgsql_context_info = saved_context_info;
395 : : }
396 [ # # ]: 0 : PG_END_TRY();
5340 397 : 0 : }
398 : :
399 : : /*
400 : : * Module load callback
401 : : */
402 : : void
403 : 0 : _PG_init(void)
404 : : {
405 : : /*
406 : : * We allow to load the SE-PostgreSQL module on single-user-mode or
407 : : * shared_preload_libraries settings only.
408 : : */
409 [ # # ]: 0 : if (IsUnderPostmaster)
410 [ # # ]: 0 : ereport(ERROR,
411 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
412 : : errmsg("sepgsql must be loaded via \"shared_preload_libraries\"")));
413 : :
414 : : /*
415 : : * Check availability of SELinux on the platform. If disabled, we cannot
416 : : * activate any SE-PostgreSQL features, and we have to skip rest of
417 : : * initialization.
418 : : */
419 [ # # ]: 0 : if (is_selinux_enabled() < 1)
420 : : {
421 : 0 : sepgsql_set_mode(SEPGSQL_MODE_DISABLED);
422 : 0 : return;
423 : : }
424 : :
425 : : /*
426 : : * sepgsql.permissive = (on|off)
427 : : *
428 : : * This variable controls performing mode of SE-PostgreSQL on user's
429 : : * session.
430 : : */
431 : 0 : DefineCustomBoolVariable("sepgsql.permissive",
432 : : "Turn on/off permissive mode in SE-PostgreSQL",
433 : : NULL,
434 : : &sepgsql_permissive,
435 : : false,
436 : : PGC_SIGHUP,
437 : : GUC_NOT_IN_SAMPLE,
438 : : NULL,
439 : : NULL,
440 : : NULL);
441 : :
442 : : /*
443 : : * sepgsql.debug_audit = (on|off)
444 : : *
445 : : * This variable allows users to turn on/off audit logs on access control
446 : : * decisions, independent from auditallow/auditdeny setting in the
447 : : * security policy. We intend to use this option for debugging purpose.
448 : : */
449 : 0 : DefineCustomBoolVariable("sepgsql.debug_audit",
450 : : "Turn on/off debug audit messages",
451 : : NULL,
452 : : &sepgsql_debug_audit,
453 : : false,
454 : : PGC_USERSET,
455 : : GUC_NOT_IN_SAMPLE,
456 : : NULL,
457 : : NULL,
458 : : NULL);
459 : :
1293 tgl@sss.pgh.pa.us 460 : 0 : MarkGUCPrefixReserved("sepgsql");
461 : :
462 : : /* Initialize userspace access vector cache */
5119 rhaas@postgresql.org 463 : 0 : sepgsql_avc_init();
464 : :
465 : : /* Initialize security label of the client and related stuff */
4952 466 : 0 : sepgsql_init_client_label();
467 : :
468 : : /* Security label provider hook */
5340 469 : 0 : register_label_provider(SEPGSQL_LABEL_TAG,
470 : : sepgsql_object_relabel);
471 : :
472 : : /* Object access hook */
473 : 0 : next_object_access_hook = object_access_hook;
474 : 0 : object_access_hook = sepgsql_object_access;
475 : :
476 : : /* DML permission check */
477 : 0 : next_exec_check_perms_hook = ExecutorCheckPerms_hook;
478 : 0 : ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
479 : :
480 : : /* ProcessUtility hook */
481 : 0 : next_ProcessUtility_hook = ProcessUtility_hook;
482 : 0 : ProcessUtility_hook = sepgsql_utility_command;
483 : :
484 : : /* init contextual info */
5008 485 : 0 : memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info));
486 : : }
|