1 /* -------------------------------------------------------------------------
2  *
3  * contrib/sepgsql/dml.c
4  *
5  * Routines to handle DML permission checks
6  *
7  * Copyright (c) 2010-2016, PostgreSQL Global Development Group
8  *
9  * -------------------------------------------------------------------------
10  */
11 #include "postgres.h"
12 
13 #include "access/htup_details.h"
14 #include "access/sysattr.h"
15 #include "access/tupdesc.h"
16 #include "catalog/catalog.h"
17 #include "catalog/heap.h"
18 #include "catalog/dependency.h"
19 #include "catalog/pg_attribute.h"
20 #include "catalog/pg_class.h"
21 #include "catalog/pg_inherits_fn.h"
22 #include "commands/seclabel.h"
23 #include "commands/tablecmds.h"
24 #include "executor/executor.h"
25 #include "nodes/bitmapset.h"
26 #include "utils/lsyscache.h"
27 #include "utils/syscache.h"
28 
29 #include "sepgsql.h"
30 
31 /*
32  * fixup_whole_row_references
33  *
34  * When user references a whole-row Var, it is equivalent to referencing
35  * all the user columns (not system columns). So, we need to fix up the
36  * given bitmapset, if it contains a whole-row reference.
37  */
38 static Bitmapset *
fixup_whole_row_references(Oid relOid,Bitmapset * columns)39 fixup_whole_row_references(Oid relOid, Bitmapset *columns)
40 {
41 	Bitmapset  *result;
42 	HeapTuple	tuple;
43 	AttrNumber	natts;
44 	AttrNumber	attno;
45 	int			index;
46 
47 	/* if no whole-row references, nothing to do */
48 	index = InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber;
49 	if (!bms_is_member(index, columns))
50 		return columns;
51 
52 	/* obtain number of attributes */
53 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
54 	if (!HeapTupleIsValid(tuple))
55 		elog(ERROR, "cache lookup failed for relation %u", relOid);
56 	natts = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
57 	ReleaseSysCache(tuple);
58 
59 	/* remove bit 0 from column set, add in all the non-dropped columns */
60 	result = bms_copy(columns);
61 	result = bms_del_member(result, index);
62 
63 	for (attno = 1; attno <= natts; attno++)
64 	{
65 		tuple = SearchSysCache2(ATTNUM,
66 								ObjectIdGetDatum(relOid),
67 								Int16GetDatum(attno));
68 		if (!HeapTupleIsValid(tuple))
69 			continue;			/* unexpected case, should we error? */
70 
71 		if (!((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped)
72 		{
73 			index = attno - FirstLowInvalidHeapAttributeNumber;
74 			result = bms_add_member(result, index);
75 		}
76 
77 		ReleaseSysCache(tuple);
78 	}
79 	return result;
80 }
81 
82 /*
83  * fixup_inherited_columns
84  *
85  * When user is querying on a table with children, it implicitly accesses
86  * child tables also. So, we also need to check security label of child
87  * tables and columns, but here is no guarantee attribute numbers are
88  * same between the parent ans children.
89  * It returns a bitmapset which contains attribute number of the child
90  * table based on the given bitmapset of the parent.
91  */
92 static Bitmapset *
fixup_inherited_columns(Oid parentId,Oid childId,Bitmapset * columns)93 fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns)
94 {
95 	Bitmapset  *result = NULL;
96 	int			index;
97 
98 	/*
99 	 * obviously, no need to do anything here
100 	 */
101 	if (parentId == childId)
102 		return columns;
103 
104 	index = -1;
105 	while ((index = bms_next_member(columns, index)) >= 0)
106 	{
107 		/* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */
108 		AttrNumber	attno = index + FirstLowInvalidHeapAttributeNumber;
109 		char	   *attname;
110 
111 		/*
112 		 * whole-row-reference shall be fixed-up later
113 		 */
114 		if (attno == InvalidAttrNumber)
115 		{
116 			result = bms_add_member(result, index);
117 			continue;
118 		}
119 
120 		attname = get_attname(parentId, attno);
121 		if (!attname)
122 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
123 				 attno, parentId);
124 		attno = get_attnum(childId, attname);
125 		if (attno == InvalidAttrNumber)
126 			elog(ERROR, "cache lookup failed for attribute %s of relation %u",
127 				 attname, childId);
128 
129 		result = bms_add_member(result,
130 								attno - FirstLowInvalidHeapAttributeNumber);
131 
132 		pfree(attname);
133 	}
134 
135 	return result;
136 }
137 
138 /*
139  * check_relation_privileges
140  *
141  * It actually checks required permissions on a certain relation
142  * and its columns.
143  */
144 static bool
check_relation_privileges(Oid relOid,Bitmapset * selected,Bitmapset * inserted,Bitmapset * updated,uint32 required,bool abort_on_violation)145 check_relation_privileges(Oid relOid,
146 						  Bitmapset *selected,
147 						  Bitmapset *inserted,
148 						  Bitmapset *updated,
149 						  uint32 required,
150 						  bool abort_on_violation)
151 {
152 	ObjectAddress object;
153 	char	   *audit_name;
154 	Bitmapset  *columns;
155 	int			index;
156 	char		relkind = get_rel_relkind(relOid);
157 	bool		result = true;
158 
159 	/*
160 	 * Hardwired Policies: SE-PostgreSQL enforces - clients cannot modify
161 	 * system catalogs using DMLs - clients cannot reference/modify toast
162 	 * relations using DMLs
163 	 */
164 	if (sepgsql_getenforce() > 0)
165 	{
166 		Oid			relnamespace = get_rel_namespace(relOid);
167 
168 		if (IsSystemNamespace(relnamespace) &&
169 			(required & (SEPG_DB_TABLE__UPDATE |
170 						 SEPG_DB_TABLE__INSERT |
171 						 SEPG_DB_TABLE__DELETE)) != 0)
172 			ereport(ERROR,
173 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
174 					 errmsg("SELinux: hardwired security policy violation")));
175 
176 		if (relkind == RELKIND_TOASTVALUE)
177 			ereport(ERROR,
178 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
179 					 errmsg("SELinux: hardwired security policy violation")));
180 	}
181 
182 	/*
183 	 * Check permissions on the relation
184 	 */
185 	object.classId = RelationRelationId;
186 	object.objectId = relOid;
187 	object.objectSubId = 0;
188 	audit_name = getObjectIdentity(&object);
189 	switch (relkind)
190 	{
191 		case RELKIND_RELATION:
192 			result = sepgsql_avc_check_perms(&object,
193 											 SEPG_CLASS_DB_TABLE,
194 											 required,
195 											 audit_name,
196 											 abort_on_violation);
197 			break;
198 
199 		case RELKIND_SEQUENCE:
200 			Assert((required & ~SEPG_DB_TABLE__SELECT) == 0);
201 
202 			if (required & SEPG_DB_TABLE__SELECT)
203 				result = sepgsql_avc_check_perms(&object,
204 												 SEPG_CLASS_DB_SEQUENCE,
205 												 SEPG_DB_SEQUENCE__GET_VALUE,
206 												 audit_name,
207 												 abort_on_violation);
208 			break;
209 
210 		case RELKIND_VIEW:
211 			result = sepgsql_avc_check_perms(&object,
212 											 SEPG_CLASS_DB_VIEW,
213 											 SEPG_DB_VIEW__EXPAND,
214 											 audit_name,
215 											 abort_on_violation);
216 			break;
217 
218 		default:
219 			/* nothing to be checked */
220 			break;
221 	}
222 	pfree(audit_name);
223 
224 	/*
225 	 * Only columns owned by relations shall be checked
226 	 */
227 	if (relkind != RELKIND_RELATION)
228 		return true;
229 
230 	/*
231 	 * Check permissions on the columns
232 	 */
233 	selected = fixup_whole_row_references(relOid, selected);
234 	inserted = fixup_whole_row_references(relOid, inserted);
235 	updated = fixup_whole_row_references(relOid, updated);
236 	columns = bms_union(selected, bms_union(inserted, updated));
237 
238 	while ((index = bms_first_member(columns)) >= 0)
239 	{
240 		AttrNumber	attnum;
241 		uint32		column_perms = 0;
242 
243 		if (bms_is_member(index, selected))
244 			column_perms |= SEPG_DB_COLUMN__SELECT;
245 		if (bms_is_member(index, inserted))
246 		{
247 			if (required & SEPG_DB_TABLE__INSERT)
248 				column_perms |= SEPG_DB_COLUMN__INSERT;
249 		}
250 		if (bms_is_member(index, updated))
251 		{
252 			if (required & SEPG_DB_TABLE__UPDATE)
253 				column_perms |= SEPG_DB_COLUMN__UPDATE;
254 		}
255 		if (column_perms == 0)
256 			continue;
257 
258 		/* obtain column's permission */
259 		attnum = index + FirstLowInvalidHeapAttributeNumber;
260 
261 		object.classId = RelationRelationId;
262 		object.objectId = relOid;
263 		object.objectSubId = attnum;
264 		audit_name = getObjectDescription(&object);
265 
266 		result = sepgsql_avc_check_perms(&object,
267 										 SEPG_CLASS_DB_COLUMN,
268 										 column_perms,
269 										 audit_name,
270 										 abort_on_violation);
271 		pfree(audit_name);
272 
273 		if (!result)
274 			return result;
275 	}
276 	return true;
277 }
278 
279 /*
280  * sepgsql_dml_privileges
281  *
282  * Entrypoint of the DML permission checks
283  */
284 bool
sepgsql_dml_privileges(List * rangeTabls,bool abort_on_violation)285 sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
286 {
287 	ListCell   *lr;
288 
289 	foreach(lr, rangeTabls)
290 	{
291 		RangeTblEntry *rte = lfirst(lr);
292 		uint32		required = 0;
293 		List	   *tableIds;
294 		ListCell   *li;
295 
296 		/*
297 		 * Only regular relations shall be checked
298 		 */
299 		if (rte->rtekind != RTE_RELATION)
300 			continue;
301 
302 		/*
303 		 * Find out required permissions
304 		 */
305 		if (rte->requiredPerms & ACL_SELECT)
306 			required |= SEPG_DB_TABLE__SELECT;
307 		if (rte->requiredPerms & ACL_INSERT)
308 			required |= SEPG_DB_TABLE__INSERT;
309 		if (rte->requiredPerms & ACL_UPDATE)
310 		{
311 			if (!bms_is_empty(rte->updatedCols))
312 				required |= SEPG_DB_TABLE__UPDATE;
313 			else
314 				required |= SEPG_DB_TABLE__LOCK;
315 		}
316 		if (rte->requiredPerms & ACL_DELETE)
317 			required |= SEPG_DB_TABLE__DELETE;
318 
319 		/*
320 		 * Skip, if nothing to be checked
321 		 */
322 		if (required == 0)
323 			continue;
324 
325 		/*
326 		 * If this RangeTblEntry is also supposed to reference inherited
327 		 * tables, we need to check security label of the child tables. So, we
328 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
329 		 * checker routine will be invoked for each relations.
330 		 */
331 		if (!rte->inh)
332 			tableIds = list_make1_oid(rte->relid);
333 		else
334 			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
335 
336 		foreach(li, tableIds)
337 		{
338 			Oid			tableOid = lfirst_oid(li);
339 			Bitmapset  *selectedCols;
340 			Bitmapset  *insertedCols;
341 			Bitmapset  *updatedCols;
342 
343 			/*
344 			 * child table has different attribute numbers, so we need to fix
345 			 * up them.
346 			 */
347 			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
348 												   rte->selectedCols);
349 			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
350 												   rte->insertedCols);
351 			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
352 												  rte->updatedCols);
353 
354 			/*
355 			 * check permissions on individual tables
356 			 */
357 			if (!check_relation_privileges(tableOid,
358 										   selectedCols,
359 										   insertedCols,
360 										   updatedCols,
361 										   required, abort_on_violation))
362 				return false;
363 		}
364 		list_free(tableIds);
365 	}
366 	return true;
367 }
368