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