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