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