1 /* -------------------------------------------------------------------------
2  *
3  * contrib/sepgsql/dml.c
4  *
5  * Routines to handle DML permission checks
6  *
7  * Copyright (c) 2010-2017, 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 		case RELKIND_PARTITIONED_TABLE:
193 			result = sepgsql_avc_check_perms(&object,
194 											 SEPG_CLASS_DB_TABLE,
195 											 required,
196 											 audit_name,
197 											 abort_on_violation);
198 			break;
199 
200 		case RELKIND_SEQUENCE:
201 			Assert((required & ~SEPG_DB_TABLE__SELECT) == 0);
202 
203 			if (required & SEPG_DB_TABLE__SELECT)
204 				result = sepgsql_avc_check_perms(&object,
205 												 SEPG_CLASS_DB_SEQUENCE,
206 												 SEPG_DB_SEQUENCE__GET_VALUE,
207 												 audit_name,
208 												 abort_on_violation);
209 			break;
210 
211 		case RELKIND_VIEW:
212 			result = sepgsql_avc_check_perms(&object,
213 											 SEPG_CLASS_DB_VIEW,
214 											 SEPG_DB_VIEW__EXPAND,
215 											 audit_name,
216 											 abort_on_violation);
217 			break;
218 
219 		default:
220 			/* nothing to be checked */
221 			break;
222 	}
223 	pfree(audit_name);
224 
225 	/*
226 	 * Only columns owned by relations shall be checked
227 	 */
228 	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
229 		return true;
230 
231 	/*
232 	 * Check permissions on the columns
233 	 */
234 	selected = fixup_whole_row_references(relOid, selected);
235 	inserted = fixup_whole_row_references(relOid, inserted);
236 	updated = fixup_whole_row_references(relOid, updated);
237 	columns = bms_union(selected, bms_union(inserted, updated));
238 
239 	while ((index = bms_first_member(columns)) >= 0)
240 	{
241 		AttrNumber	attnum;
242 		uint32		column_perms = 0;
243 
244 		if (bms_is_member(index, selected))
245 			column_perms |= SEPG_DB_COLUMN__SELECT;
246 		if (bms_is_member(index, inserted))
247 		{
248 			if (required & SEPG_DB_TABLE__INSERT)
249 				column_perms |= SEPG_DB_COLUMN__INSERT;
250 		}
251 		if (bms_is_member(index, updated))
252 		{
253 			if (required & SEPG_DB_TABLE__UPDATE)
254 				column_perms |= SEPG_DB_COLUMN__UPDATE;
255 		}
256 		if (column_perms == 0)
257 			continue;
258 
259 		/* obtain column's permission */
260 		attnum = index + FirstLowInvalidHeapAttributeNumber;
261 
262 		object.classId = RelationRelationId;
263 		object.objectId = relOid;
264 		object.objectSubId = attnum;
265 		audit_name = getObjectDescription(&object);
266 
267 		result = sepgsql_avc_check_perms(&object,
268 										 SEPG_CLASS_DB_COLUMN,
269 										 column_perms,
270 										 audit_name,
271 										 abort_on_violation);
272 		pfree(audit_name);
273 
274 		if (!result)
275 			return result;
276 	}
277 	return true;
278 }
279 
280 /*
281  * sepgsql_dml_privileges
282  *
283  * Entrypoint of the DML permission checks
284  */
285 bool
sepgsql_dml_privileges(List * rangeTabls,bool abort_on_violation)286 sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
287 {
288 	ListCell   *lr;
289 
290 	foreach(lr, rangeTabls)
291 	{
292 		RangeTblEntry *rte = lfirst(lr);
293 		uint32		required = 0;
294 		List	   *tableIds;
295 		ListCell   *li;
296 
297 		/*
298 		 * Only regular relations shall be checked
299 		 */
300 		if (rte->rtekind != RTE_RELATION)
301 			continue;
302 
303 		/*
304 		 * Find out required permissions
305 		 */
306 		if (rte->requiredPerms & ACL_SELECT)
307 			required |= SEPG_DB_TABLE__SELECT;
308 		if (rte->requiredPerms & ACL_INSERT)
309 			required |= SEPG_DB_TABLE__INSERT;
310 		if (rte->requiredPerms & ACL_UPDATE)
311 		{
312 			if (!bms_is_empty(rte->updatedCols))
313 				required |= SEPG_DB_TABLE__UPDATE;
314 			else
315 				required |= SEPG_DB_TABLE__LOCK;
316 		}
317 		if (rte->requiredPerms & ACL_DELETE)
318 			required |= SEPG_DB_TABLE__DELETE;
319 
320 		/*
321 		 * Skip, if nothing to be checked
322 		 */
323 		if (required == 0)
324 			continue;
325 
326 		/*
327 		 * If this RangeTblEntry is also supposed to reference inherited
328 		 * tables, we need to check security label of the child tables. So, we
329 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
330 		 * checker routine will be invoked for each relations.
331 		 */
332 		if (!rte->inh)
333 			tableIds = list_make1_oid(rte->relid);
334 		else
335 			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
336 
337 		foreach(li, tableIds)
338 		{
339 			Oid			tableOid = lfirst_oid(li);
340 			Bitmapset  *selectedCols;
341 			Bitmapset  *insertedCols;
342 			Bitmapset  *updatedCols;
343 
344 			/*
345 			 * child table has different attribute numbers, so we need to fix
346 			 * up them.
347 			 */
348 			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
349 												   rte->selectedCols);
350 			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
351 												   rte->insertedCols);
352 			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
353 												  rte->updatedCols);
354 
355 			/*
356 			 * check permissions on individual tables
357 			 */
358 			if (!check_relation_privileges(tableOid,
359 										   selectedCols,
360 										   insertedCols,
361 										   updatedCols,
362 										   required, abort_on_violation))
363 				return false;
364 		}
365 		list_free(tableIds);
366 	}
367 	return true;
368 }
369