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