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