1 /* -------------------------------------------------------------------------
2  *
3  * contrib/sepgsql/dml.c
4  *
5  * Routines to handle DML permission checks
6  *
7  * Copyright (c) 2010-2018, 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.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, false);
121 		attno = get_attnum(childId, attname);
122 		if (attno == InvalidAttrNumber)
123 			elog(ERROR, "cache lookup failed for attribute %s of relation %u",
124 				 attname, childId);
125 
126 		result = bms_add_member(result,
127 								attno - FirstLowInvalidHeapAttributeNumber);
128 
129 		pfree(attname);
130 	}
131 
132 	return result;
133 }
134 
135 /*
136  * check_relation_privileges
137  *
138  * It actually checks required permissions on a certain relation
139  * and its columns.
140  */
141 static bool
check_relation_privileges(Oid relOid,Bitmapset * selected,Bitmapset * inserted,Bitmapset * updated,uint32 required,bool abort_on_violation)142 check_relation_privileges(Oid relOid,
143 						  Bitmapset *selected,
144 						  Bitmapset *inserted,
145 						  Bitmapset *updated,
146 						  uint32 required,
147 						  bool abort_on_violation)
148 {
149 	ObjectAddress object;
150 	char	   *audit_name;
151 	Bitmapset  *columns;
152 	int			index;
153 	char		relkind = get_rel_relkind(relOid);
154 	bool		result = true;
155 
156 	/*
157 	 * Hardwired Policies: SE-PostgreSQL enforces - clients cannot modify
158 	 * system catalogs using DMLs - clients cannot reference/modify toast
159 	 * relations using DMLs
160 	 */
161 	if (sepgsql_getenforce() > 0)
162 	{
163 		Oid			relnamespace = get_rel_namespace(relOid);
164 
165 		if (IsSystemNamespace(relnamespace) &&
166 			(required & (SEPG_DB_TABLE__UPDATE |
167 						 SEPG_DB_TABLE__INSERT |
168 						 SEPG_DB_TABLE__DELETE)) != 0)
169 			ereport(ERROR,
170 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
171 					 errmsg("SELinux: hardwired security policy violation")));
172 
173 		if (relkind == RELKIND_TOASTVALUE)
174 			ereport(ERROR,
175 					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
176 					 errmsg("SELinux: hardwired security policy violation")));
177 	}
178 
179 	/*
180 	 * Check permissions on the relation
181 	 */
182 	object.classId = RelationRelationId;
183 	object.objectId = relOid;
184 	object.objectSubId = 0;
185 	audit_name = getObjectIdentity(&object);
186 	switch (relkind)
187 	{
188 		case RELKIND_RELATION:
189 		case RELKIND_PARTITIONED_TABLE:
190 			result = sepgsql_avc_check_perms(&object,
191 											 SEPG_CLASS_DB_TABLE,
192 											 required,
193 											 audit_name,
194 											 abort_on_violation);
195 			break;
196 
197 		case RELKIND_SEQUENCE:
198 			Assert((required & ~SEPG_DB_TABLE__SELECT) == 0);
199 
200 			if (required & SEPG_DB_TABLE__SELECT)
201 				result = sepgsql_avc_check_perms(&object,
202 												 SEPG_CLASS_DB_SEQUENCE,
203 												 SEPG_DB_SEQUENCE__GET_VALUE,
204 												 audit_name,
205 												 abort_on_violation);
206 			break;
207 
208 		case RELKIND_VIEW:
209 			result = sepgsql_avc_check_perms(&object,
210 											 SEPG_CLASS_DB_VIEW,
211 											 SEPG_DB_VIEW__EXPAND,
212 											 audit_name,
213 											 abort_on_violation);
214 			break;
215 
216 		default:
217 			/* nothing to be checked */
218 			break;
219 	}
220 	pfree(audit_name);
221 
222 	/*
223 	 * Only columns owned by relations shall be checked
224 	 */
225 	if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
226 		return true;
227 
228 	/*
229 	 * Check permissions on the columns
230 	 */
231 	selected = fixup_whole_row_references(relOid, selected);
232 	inserted = fixup_whole_row_references(relOid, inserted);
233 	updated = fixup_whole_row_references(relOid, updated);
234 	columns = bms_union(selected, bms_union(inserted, updated));
235 
236 	while ((index = bms_first_member(columns)) >= 0)
237 	{
238 		AttrNumber	attnum;
239 		uint32		column_perms = 0;
240 
241 		if (bms_is_member(index, selected))
242 			column_perms |= SEPG_DB_COLUMN__SELECT;
243 		if (bms_is_member(index, inserted))
244 		{
245 			if (required & SEPG_DB_TABLE__INSERT)
246 				column_perms |= SEPG_DB_COLUMN__INSERT;
247 		}
248 		if (bms_is_member(index, updated))
249 		{
250 			if (required & SEPG_DB_TABLE__UPDATE)
251 				column_perms |= SEPG_DB_COLUMN__UPDATE;
252 		}
253 		if (column_perms == 0)
254 			continue;
255 
256 		/* obtain column's permission */
257 		attnum = index + FirstLowInvalidHeapAttributeNumber;
258 
259 		object.classId = RelationRelationId;
260 		object.objectId = relOid;
261 		object.objectSubId = attnum;
262 		audit_name = getObjectDescription(&object);
263 
264 		result = sepgsql_avc_check_perms(&object,
265 										 SEPG_CLASS_DB_COLUMN,
266 										 column_perms,
267 										 audit_name,
268 										 abort_on_violation);
269 		pfree(audit_name);
270 
271 		if (!result)
272 			return result;
273 	}
274 	return true;
275 }
276 
277 /*
278  * sepgsql_dml_privileges
279  *
280  * Entrypoint of the DML permission checks
281  */
282 bool
sepgsql_dml_privileges(List * rangeTabls,bool abort_on_violation)283 sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
284 {
285 	ListCell   *lr;
286 
287 	foreach(lr, rangeTabls)
288 	{
289 		RangeTblEntry *rte = lfirst(lr);
290 		uint32		required = 0;
291 		List	   *tableIds;
292 		ListCell   *li;
293 
294 		/*
295 		 * Only regular relations shall be checked
296 		 */
297 		if (rte->rtekind != RTE_RELATION)
298 			continue;
299 
300 		/*
301 		 * Find out required permissions
302 		 */
303 		if (rte->requiredPerms & ACL_SELECT)
304 			required |= SEPG_DB_TABLE__SELECT;
305 		if (rte->requiredPerms & ACL_INSERT)
306 			required |= SEPG_DB_TABLE__INSERT;
307 		if (rte->requiredPerms & ACL_UPDATE)
308 		{
309 			if (!bms_is_empty(rte->updatedCols))
310 				required |= SEPG_DB_TABLE__UPDATE;
311 			else
312 				required |= SEPG_DB_TABLE__LOCK;
313 		}
314 		if (rte->requiredPerms & ACL_DELETE)
315 			required |= SEPG_DB_TABLE__DELETE;
316 
317 		/*
318 		 * Skip, if nothing to be checked
319 		 */
320 		if (required == 0)
321 			continue;
322 
323 		/*
324 		 * If this RangeTblEntry is also supposed to reference inherited
325 		 * tables, we need to check security label of the child tables. So, we
326 		 * expand rte->relid into list of OIDs of inheritance hierarchy, then
327 		 * checker routine will be invoked for each relations.
328 		 */
329 		if (!rte->inh)
330 			tableIds = list_make1_oid(rte->relid);
331 		else
332 			tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
333 
334 		foreach(li, tableIds)
335 		{
336 			Oid			tableOid = lfirst_oid(li);
337 			Bitmapset  *selectedCols;
338 			Bitmapset  *insertedCols;
339 			Bitmapset  *updatedCols;
340 
341 			/*
342 			 * child table has different attribute numbers, so we need to fix
343 			 * up them.
344 			 */
345 			selectedCols = fixup_inherited_columns(rte->relid, tableOid,
346 												   rte->selectedCols);
347 			insertedCols = fixup_inherited_columns(rte->relid, tableOid,
348 												   rte->insertedCols);
349 			updatedCols = fixup_inherited_columns(rte->relid, tableOid,
350 												  rte->updatedCols);
351 
352 			/*
353 			 * check permissions on individual tables
354 			 */
355 			if (!check_relation_privileges(tableOid,
356 										   selectedCols,
357 										   insertedCols,
358 										   updatedCols,
359 										   required, abort_on_violation))
360 				return false;
361 		}
362 		list_free(tableIds);
363 	}
364 	return true;
365 }
366