Age Owner Branch data TLA Line data Source code
1 : : /* -------------------------------------------------------------------------
2 : : *
3 : : * contrib/sepgsql/dml.c
4 : : *
5 : : * Routines to handle DML permission checks
6 : : *
7 : : * Copyright (c) 2010-2024, 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 "parser/parsetree.h"
27 : : #include "sepgsql.h"
28 : : #include "utils/lsyscache.h"
29 : : #include "utils/syscache.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 *
4830 rhaas@postgresql.org 39 :UBC 0 : 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 : 0 : index = InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber;
49 [ # # ]: 0 : if (!bms_is_member(index, columns))
50 : 0 : return columns;
51 : :
52 : : /* obtain number of attributes */
53 : 0 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
54 [ # # ]: 0 : if (!HeapTupleIsValid(tuple))
55 [ # # ]: 0 : elog(ERROR, "cache lookup failed for relation %u", relOid);
56 : 0 : natts = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
57 : 0 : ReleaseSysCache(tuple);
58 : :
59 : : /* remove bit 0 from column set, add in all the non-dropped columns */
60 : 0 : result = bms_copy(columns);
61 : 0 : result = bms_del_member(result, index);
62 : :
4753 bruce@momjian.us 63 [ # # ]: 0 : for (attno = 1; attno <= natts; attno++)
64 : : {
4830 rhaas@postgresql.org 65 : 0 : tuple = SearchSysCache2(ATTNUM,
66 : : ObjectIdGetDatum(relOid),
67 : : Int16GetDatum(attno));
68 [ # # ]: 0 : if (!HeapTupleIsValid(tuple))
1459 tgl@sss.pgh.pa.us 69 : 0 : continue; /* unexpected case, should we error? */
70 : :
71 [ # # ]: 0 : if (!((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped)
72 : : {
73 : 0 : index = attno - FirstLowInvalidHeapAttributeNumber;
74 : 0 : result = bms_add_member(result, index);
75 : : }
76 : :
4830 rhaas@postgresql.org 77 : 0 : ReleaseSysCache(tuple);
78 : : }
79 : 0 : 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 there is no guarantee attribute numbers are
88 : : * same between the parent and 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 *
93 : 0 : fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns)
94 : : {
95 : 0 : Bitmapset *result = NULL;
96 : : int index;
97 : :
98 : : /*
99 : : * obviously, no need to do anything here
100 : : */
101 [ # # ]: 0 : if (parentId == childId)
102 : 0 : return columns;
103 : :
3425 tgl@sss.pgh.pa.us 104 : 0 : index = -1;
105 [ # # ]: 0 : while ((index = bms_next_member(columns, index)) >= 0)
106 : : {
107 : : /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */
108 : 0 : AttrNumber attno = index + FirstLowInvalidHeapAttributeNumber;
109 : : char *attname;
110 : :
111 : : /*
112 : : * whole-row-reference shall be fixed-up later
113 : : */
4830 rhaas@postgresql.org 114 [ # # ]: 0 : if (attno == InvalidAttrNumber)
115 : : {
116 : 0 : result = bms_add_member(result, index);
117 : 0 : continue;
118 : : }
119 : :
2253 alvherre@alvh.no-ip. 120 : 0 : attname = get_attname(parentId, attno, false);
4830 rhaas@postgresql.org 121 : 0 : attno = get_attnum(childId, attname);
122 [ # # ]: 0 : if (attno == InvalidAttrNumber)
123 [ # # ]: 0 : elog(ERROR, "cache lookup failed for attribute %s of relation %u",
124 : : attname, childId);
125 : :
3425 tgl@sss.pgh.pa.us 126 : 0 : result = bms_add_member(result,
127 : : attno - FirstLowInvalidHeapAttributeNumber);
128 : :
4830 rhaas@postgresql.org 129 : 0 : pfree(attname);
130 : : }
131 : :
132 : 0 : 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
142 : 0 : 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;
4609 153 : 0 : char relkind = get_rel_relkind(relOid);
4830 154 : 0 : 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 [ # # ]: 0 : if (sepgsql_getenforce() > 0)
162 : : {
1803 tgl@sss.pgh.pa.us 163 [ # # ]: 0 : if ((required & (SEPG_DB_TABLE__UPDATE |
164 : : SEPG_DB_TABLE__INSERT |
165 [ # # ]: 0 : SEPG_DB_TABLE__DELETE)) != 0 &&
166 : 0 : IsCatalogRelationOid(relOid))
4830 rhaas@postgresql.org 167 [ # # ]: 0 : ereport(ERROR,
168 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
169 : : errmsg("SELinux: hardwired security policy violation")));
170 : :
171 [ # # ]: 0 : if (relkind == RELKIND_TOASTVALUE)
172 [ # # ]: 0 : ereport(ERROR,
173 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
174 : : errmsg("SELinux: hardwired security policy violation")));
175 : : }
176 : :
177 : : /*
178 : : * Check permissions on the relation
179 : : */
4609 180 : 0 : object.classId = RelationRelationId;
181 : 0 : object.objectId = relOid;
182 : 0 : object.objectSubId = 0;
1369 michael@paquier.xyz 183 : 0 : audit_name = getObjectIdentity(&object, false);
4830 rhaas@postgresql.org 184 [ # # # # ]: 0 : switch (relkind)
185 : : {
186 : 0 : case RELKIND_RELATION:
187 : : case RELKIND_PARTITIONED_TABLE:
4609 188 : 0 : result = sepgsql_avc_check_perms(&object,
189 : : SEPG_CLASS_DB_TABLE,
190 : : required,
191 : : audit_name,
192 : : abort_on_violation);
4830 193 : 0 : break;
194 : :
195 : 0 : case RELKIND_SEQUENCE:
196 [ # # ]: 0 : Assert((required & ~SEPG_DB_TABLE__SELECT) == 0);
197 : :
198 [ # # ]: 0 : if (required & SEPG_DB_TABLE__SELECT)
4609 199 : 0 : result = sepgsql_avc_check_perms(&object,
200 : : SEPG_CLASS_DB_SEQUENCE,
201 : : SEPG_DB_SEQUENCE__GET_VALUE,
202 : : audit_name,
203 : : abort_on_violation);
4820 204 : 0 : break;
205 : :
4830 206 : 0 : case RELKIND_VIEW:
4609 207 : 0 : result = sepgsql_avc_check_perms(&object,
208 : : SEPG_CLASS_DB_VIEW,
209 : : SEPG_DB_VIEW__EXPAND,
210 : : audit_name,
211 : : abort_on_violation);
4820 212 : 0 : break;
213 : :
4830 214 : 0 : default:
215 : : /* nothing to be checked */
4820 216 : 0 : break;
217 : : }
218 : 0 : pfree(audit_name);
219 : :
220 : : /*
221 : : * Only columns owned by relations shall be checked
222 : : */
2562 mail@joeconway.com 223 [ # # # # ]: 0 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
4820 rhaas@postgresql.org 224 : 0 : return true;
225 : :
226 : : /*
227 : : * Check permissions on the columns
228 : : */
4830 229 : 0 : selected = fixup_whole_row_references(relOid, selected);
3264 andres@anarazel.de 230 : 0 : inserted = fixup_whole_row_references(relOid, inserted);
231 : 0 : updated = fixup_whole_row_references(relOid, updated);
232 : 0 : columns = bms_union(selected, bms_union(inserted, updated));
233 : :
409 tgl@sss.pgh.pa.us 234 : 0 : index = -1;
235 [ # # ]: 0 : while ((index = bms_next_member(columns, index)) >= 0)
236 : : {
237 : : AttrNumber attnum;
4830 rhaas@postgresql.org 238 : 0 : uint32 column_perms = 0;
239 : :
240 [ # # ]: 0 : if (bms_is_member(index, selected))
241 : 0 : column_perms |= SEPG_DB_COLUMN__SELECT;
3264 andres@anarazel.de 242 [ # # ]: 0 : if (bms_is_member(index, inserted))
243 : : {
4830 rhaas@postgresql.org 244 [ # # ]: 0 : if (required & SEPG_DB_TABLE__INSERT)
245 : 0 : column_perms |= SEPG_DB_COLUMN__INSERT;
246 : : }
3264 andres@anarazel.de 247 [ # # ]: 0 : if (bms_is_member(index, updated))
248 : : {
249 [ # # ]: 0 : if (required & SEPG_DB_TABLE__UPDATE)
250 : 0 : column_perms |= SEPG_DB_COLUMN__UPDATE;
251 : : }
4830 rhaas@postgresql.org 252 [ # # ]: 0 : if (column_perms == 0)
253 : 0 : continue;
254 : :
255 : : /* obtain column's permission */
256 : 0 : attnum = index + FirstLowInvalidHeapAttributeNumber;
257 : :
4820 258 : 0 : object.classId = RelationRelationId;
259 : 0 : object.objectId = relOid;
260 : 0 : object.objectSubId = attnum;
1369 michael@paquier.xyz 261 : 0 : audit_name = getObjectDescription(&object, false);
262 : :
4609 rhaas@postgresql.org 263 : 0 : result = sepgsql_avc_check_perms(&object,
264 : : SEPG_CLASS_DB_COLUMN,
265 : : column_perms,
266 : : audit_name,
267 : : abort_on_violation);
4820 268 : 0 : pfree(audit_name);
269 : :
4830 270 [ # # ]: 0 : if (!result)
271 : 0 : return result;
272 : : }
273 : 0 : return true;
274 : : }
275 : :
276 : : /*
277 : : * sepgsql_dml_privileges
278 : : *
279 : : * Entrypoint of the DML permission checks
280 : : */
281 : : bool
495 alvherre@alvh.no-ip. 282 : 0 : sepgsql_dml_privileges(List *rangeTbls, List *rteperminfos,
283 : : bool abort_on_violation)
284 : : {
285 : : ListCell *lr;
286 : :
287 [ # # # # : 0 : foreach(lr, rteperminfos)
# # ]
288 : : {
289 : 0 : RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, lr);
4753 bruce@momjian.us 290 : 0 : uint32 required = 0;
291 : : List *tableIds;
292 : : ListCell *li;
293 : :
294 : : /*
295 : : * Find out required permissions
296 : : */
495 alvherre@alvh.no-ip. 297 [ # # ]: 0 : if (perminfo->requiredPerms & ACL_SELECT)
4830 rhaas@postgresql.org 298 : 0 : required |= SEPG_DB_TABLE__SELECT;
495 alvherre@alvh.no-ip. 299 [ # # ]: 0 : if (perminfo->requiredPerms & ACL_INSERT)
4830 rhaas@postgresql.org 300 : 0 : required |= SEPG_DB_TABLE__INSERT;
495 alvherre@alvh.no-ip. 301 [ # # ]: 0 : if (perminfo->requiredPerms & ACL_UPDATE)
302 : : {
303 [ # # ]: 0 : if (!bms_is_empty(perminfo->updatedCols))
4830 rhaas@postgresql.org 304 : 0 : required |= SEPG_DB_TABLE__UPDATE;
305 : : else
306 : 0 : required |= SEPG_DB_TABLE__LOCK;
307 : : }
495 alvherre@alvh.no-ip. 308 [ # # ]: 0 : if (perminfo->requiredPerms & ACL_DELETE)
4830 rhaas@postgresql.org 309 : 0 : required |= SEPG_DB_TABLE__DELETE;
310 : :
311 : : /*
312 : : * Skip, if nothing to be checked
313 : : */
314 [ # # ]: 0 : if (required == 0)
315 : 0 : continue;
316 : :
317 : : /*
318 : : * If this RangeTblEntry is also supposed to reference inherited
319 : : * tables, we need to check security label of the child tables. So, we
320 : : * expand rte->relid into list of OIDs of inheritance hierarchy, then
321 : : * checker routine will be invoked for each relations.
322 : : */
495 alvherre@alvh.no-ip. 323 [ # # ]: 0 : if (!perminfo->inh)
324 : 0 : tableIds = list_make1_oid(perminfo->relid);
325 : : else
326 : 0 : tableIds = find_all_inheritors(perminfo->relid, NoLock, NULL);
327 : :
4753 bruce@momjian.us 328 [ # # # # : 0 : foreach(li, tableIds)
# # ]
329 : : {
4830 rhaas@postgresql.org 330 : 0 : Oid tableOid = lfirst_oid(li);
331 : : Bitmapset *selectedCols;
332 : : Bitmapset *insertedCols;
333 : : Bitmapset *updatedCols;
334 : :
335 : : /*
336 : : * child table has different attribute numbers, so we need to fix
337 : : * up them.
338 : : */
495 alvherre@alvh.no-ip. 339 : 0 : selectedCols = fixup_inherited_columns(perminfo->relid, tableOid,
340 : : perminfo->selectedCols);
341 : 0 : insertedCols = fixup_inherited_columns(perminfo->relid, tableOid,
342 : : perminfo->insertedCols);
343 : 0 : updatedCols = fixup_inherited_columns(perminfo->relid, tableOid,
344 : : perminfo->updatedCols);
345 : :
346 : : /*
347 : : * check permissions on individual tables
348 : : */
4830 rhaas@postgresql.org 349 [ # # ]: 0 : if (!check_relation_privileges(tableOid,
350 : : selectedCols,
351 : : insertedCols,
352 : : updatedCols,
353 : : required, abort_on_violation))
354 : 0 : return false;
355 : : }
356 : 0 : list_free(tableIds);
357 : : }
358 : 0 : return true;
359 : : }
|