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