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