]> granicus.if.org Git - postgresql/blob - contrib/sepgsql/dml.c
Update copyright for 2015
[postgresql] / contrib / sepgsql / dml.c
1 /* -------------------------------------------------------------------------
2  *
3  * contrib/sepgsql/dml.c
4  *
5  * Routines to handle DML permission checks
6  *
7  * Copyright (c) 2010-2015, 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 reference a whole of row, it is equivalent to reference to
35  * all the user columns (not system columns). So, we need to fix up the
36  * given bitmapset, if it contains a whole of the row reference.
37  */
38 static Bitmapset *
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 of row references, do not anything */
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         /* fix up the given 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;
70
71                 if (((Form_pg_attribute) GETSTRUCT(tuple))->attisdropped)
72                         continue;
73
74                 index = attno - FirstLowInvalidHeapAttributeNumber;
75
76                 result = bms_add_member(result, index);
77
78                 ReleaseSysCache(tuple);
79         }
80         return result;
81 }
82
83 /*
84  * fixup_inherited_columns
85  *
86  * When user is querying on a table with children, it implicitly accesses
87  * child tables also. So, we also need to check security label of child
88  * tables and columns, but here is no guarantee attribute numbers are
89  * same between the parent ans children.
90  * It returns a bitmapset which contains attribute number of the child
91  * table based on the given bitmapset of the parent.
92  */
93 static Bitmapset *
94 fixup_inherited_columns(Oid parentId, Oid childId, Bitmapset *columns)
95 {
96         Bitmapset  *result = NULL;
97         int                     index;
98
99         /*
100          * obviously, no need to do anything here
101          */
102         if (parentId == childId)
103                 return columns;
104
105         index = -1;
106         while ((index = bms_next_member(columns, index)) >= 0)
107         {
108                 /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */
109                 AttrNumber      attno = index + FirstLowInvalidHeapAttributeNumber;
110                 char       *attname;
111
112                 /*
113                  * whole-row-reference shall be fixed-up later
114                  */
115                 if (attno == InvalidAttrNumber)
116                 {
117                         result = bms_add_member(result, index);
118                         continue;
119                 }
120
121                 attname = get_attname(parentId, attno);
122                 if (!attname)
123                         elog(ERROR, "cache lookup failed for attribute %d of relation %u",
124                                  attno, parentId);
125                 attno = get_attnum(childId, attname);
126                 if (attno == InvalidAttrNumber)
127                         elog(ERROR, "cache lookup failed for attribute %s of relation %u",
128                                  attname, childId);
129
130                 result = bms_add_member(result,
131                                                                 attno - FirstLowInvalidHeapAttributeNumber);
132
133                 pfree(attname);
134         }
135
136         return result;
137 }
138
139 /*
140  * check_relation_privileges
141  *
142  * It actually checks required permissions on a certain relation
143  * and its columns.
144  */
145 static bool
146 check_relation_privileges(Oid relOid,
147                                                   Bitmapset *selected,
148                                                   Bitmapset *modified,
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                         result = sepgsql_avc_check_perms(&object,
193                                                                                          SEPG_CLASS_DB_TABLE,
194                                                                                          required,
195                                                                                          audit_name,
196                                                                                          abort_on_violation);
197                         break;
198
199                 case RELKIND_SEQUENCE:
200                         Assert((required & ~SEPG_DB_TABLE__SELECT) == 0);
201
202                         if (required & SEPG_DB_TABLE__SELECT)
203                                 result = sepgsql_avc_check_perms(&object,
204                                                                                                  SEPG_CLASS_DB_SEQUENCE,
205                                                                                                  SEPG_DB_SEQUENCE__GET_VALUE,
206                                                                                                  audit_name,
207                                                                                                  abort_on_violation);
208                         break;
209
210                 case RELKIND_VIEW:
211                         result = sepgsql_avc_check_perms(&object,
212                                                                                          SEPG_CLASS_DB_VIEW,
213                                                                                          SEPG_DB_VIEW__EXPAND,
214                                                                                          audit_name,
215                                                                                          abort_on_violation);
216                         break;
217
218                 default:
219                         /* nothing to be checked */
220                         break;
221         }
222         pfree(audit_name);
223
224         /*
225          * Only columns owned by relations shall be checked
226          */
227         if (relkind != RELKIND_RELATION)
228                 return true;
229
230         /*
231          * Check permissions on the columns
232          */
233         selected = fixup_whole_row_references(relOid, selected);
234         modified = fixup_whole_row_references(relOid, modified);
235         columns = bms_union(selected, modified);
236
237         while ((index = bms_first_member(columns)) >= 0)
238         {
239                 AttrNumber      attnum;
240                 uint32          column_perms = 0;
241
242                 if (bms_is_member(index, selected))
243                         column_perms |= SEPG_DB_COLUMN__SELECT;
244                 if (bms_is_member(index, modified))
245                 {
246                         if (required & SEPG_DB_TABLE__UPDATE)
247                                 column_perms |= SEPG_DB_COLUMN__UPDATE;
248                         if (required & SEPG_DB_TABLE__INSERT)
249                                 column_perms |= SEPG_DB_COLUMN__INSERT;
250                 }
251                 if (column_perms == 0)
252                         continue;
253
254                 /* obtain column's permission */
255                 attnum = index + FirstLowInvalidHeapAttributeNumber;
256
257                 object.classId = RelationRelationId;
258                 object.objectId = relOid;
259                 object.objectSubId = attnum;
260                 audit_name = getObjectDescription(&object);
261
262                 result = sepgsql_avc_check_perms(&object,
263                                                                                  SEPG_CLASS_DB_COLUMN,
264                                                                                  column_perms,
265                                                                                  audit_name,
266                                                                                  abort_on_violation);
267                 pfree(audit_name);
268
269                 if (!result)
270                         return result;
271         }
272         return true;
273 }
274
275 /*
276  * sepgsql_dml_privileges
277  *
278  * Entrypoint of the DML permission checks
279  */
280 bool
281 sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
282 {
283         ListCell   *lr;
284
285         foreach(lr, rangeTabls)
286         {
287                 RangeTblEntry *rte = lfirst(lr);
288                 uint32          required = 0;
289                 List       *tableIds;
290                 ListCell   *li;
291
292                 /*
293                  * Only regular relations shall be checked
294                  */
295                 if (rte->rtekind != RTE_RELATION)
296                         continue;
297
298                 /*
299                  * Find out required permissions
300                  */
301                 if (rte->requiredPerms & ACL_SELECT)
302                         required |= SEPG_DB_TABLE__SELECT;
303                 if (rte->requiredPerms & ACL_INSERT)
304                         required |= SEPG_DB_TABLE__INSERT;
305                 if (rte->requiredPerms & ACL_UPDATE)
306                 {
307                         if (!bms_is_empty(rte->modifiedCols))
308                                 required |= SEPG_DB_TABLE__UPDATE;
309                         else
310                                 required |= SEPG_DB_TABLE__LOCK;
311                 }
312                 if (rte->requiredPerms & ACL_DELETE)
313                         required |= SEPG_DB_TABLE__DELETE;
314
315                 /*
316                  * Skip, if nothing to be checked
317                  */
318                 if (required == 0)
319                         continue;
320
321                 /*
322                  * If this RangeTblEntry is also supposed to reference inherited
323                  * tables, we need to check security label of the child tables. So, we
324                  * expand rte->relid into list of OIDs of inheritance hierarchy, then
325                  * checker routine will be invoked for each relations.
326                  */
327                 if (!rte->inh)
328                         tableIds = list_make1_oid(rte->relid);
329                 else
330                         tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
331
332                 foreach(li, tableIds)
333                 {
334                         Oid                     tableOid = lfirst_oid(li);
335                         Bitmapset  *selectedCols;
336                         Bitmapset  *modifiedCols;
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                         modifiedCols = fixup_inherited_columns(rte->relid, tableOid,
345                                                                                                    rte->modifiedCols);
346
347                         /*
348                          * check permissions on individual tables
349                          */
350                         if (!check_relation_privileges(tableOid,
351                                                                                    selectedCols,
352                                                                                    modifiedCols,
353                                                                                    required, abort_on_violation))
354                                 return false;
355                 }
356                 list_free(tableIds);
357         }
358         return true;
359 }