]> granicus.if.org Git - postgresql/blob - contrib/sepgsql/dml.c
Update copyright for 2014
[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-2014, 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         AttrNumber      attno;
97         Bitmapset  *tmpset;
98         Bitmapset  *result = NULL;
99         char       *attname;
100         int                     index;
101
102         /*
103          * obviously, no need to do anything here
104          */
105         if (parentId == childId)
106                 return columns;
107
108         tmpset = bms_copy(columns);
109         while ((index = bms_first_member(tmpset)) > 0)
110         {
111                 attno = index + FirstLowInvalidHeapAttributeNumber;
112
113                 /*
114                  * whole-row-reference shall be fixed-up later
115                  */
116                 if (attno == InvalidAttrNumber)
117                 {
118                         result = bms_add_member(result, index);
119                         continue;
120                 }
121
122                 attname = get_attname(parentId, attno);
123                 if (!attname)
124                         elog(ERROR, "cache lookup failed for attribute %d of relation %u",
125                                  attno, parentId);
126                 attno = get_attnum(childId, attname);
127                 if (attno == InvalidAttrNumber)
128                         elog(ERROR, "cache lookup failed for attribute %s of relation %u",
129                                  attname, childId);
130
131                 index = attno - FirstLowInvalidHeapAttributeNumber;
132                 result = bms_add_member(result, index);
133
134                 pfree(attname);
135         }
136         bms_free(tmpset);
137
138         return result;
139 }
140
141 /*
142  * check_relation_privileges
143  *
144  * It actually checks required permissions on a certain relation
145  * and its columns.
146  */
147 static bool
148 check_relation_privileges(Oid relOid,
149                                                   Bitmapset *selected,
150                                                   Bitmapset *modified,
151                                                   uint32 required,
152                                                   bool abort_on_violation)
153 {
154         ObjectAddress object;
155         char       *audit_name;
156         Bitmapset  *columns;
157         int                     index;
158         char            relkind = get_rel_relkind(relOid);
159         bool            result = true;
160
161         /*
162          * Hardwired Policies: SE-PostgreSQL enforces - clients cannot modify
163          * system catalogs using DMLs - clients cannot reference/modify toast
164          * relations using DMLs
165          */
166         if (sepgsql_getenforce() > 0)
167         {
168                 Oid                     relnamespace = get_rel_namespace(relOid);
169
170                 if (IsSystemNamespace(relnamespace) &&
171                         (required & (SEPG_DB_TABLE__UPDATE |
172                                                  SEPG_DB_TABLE__INSERT |
173                                                  SEPG_DB_TABLE__DELETE)) != 0)
174                         ereport(ERROR,
175                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
176                                          errmsg("SELinux: hardwired security policy violation")));
177
178                 if (relkind == RELKIND_TOASTVALUE)
179                         ereport(ERROR,
180                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
181                                          errmsg("SELinux: hardwired security policy violation")));
182         }
183
184         /*
185          * Check permissions on the relation
186          */
187         object.classId = RelationRelationId;
188         object.objectId = relOid;
189         object.objectSubId = 0;
190         audit_name = getObjectIdentity(&object);
191         switch (relkind)
192         {
193                 case RELKIND_RELATION:
194                         result = sepgsql_avc_check_perms(&object,
195                                                                                          SEPG_CLASS_DB_TABLE,
196                                                                                          required,
197                                                                                          audit_name,
198                                                                                          abort_on_violation);
199                         break;
200
201                 case RELKIND_SEQUENCE:
202                         Assert((required & ~SEPG_DB_TABLE__SELECT) == 0);
203
204                         if (required & SEPG_DB_TABLE__SELECT)
205                                 result = sepgsql_avc_check_perms(&object,
206                                                                                                  SEPG_CLASS_DB_SEQUENCE,
207                                                                                                  SEPG_DB_SEQUENCE__GET_VALUE,
208                                                                                                  audit_name,
209                                                                                                  abort_on_violation);
210                         break;
211
212                 case RELKIND_VIEW:
213                         result = sepgsql_avc_check_perms(&object,
214                                                                                          SEPG_CLASS_DB_VIEW,
215                                                                                          SEPG_DB_VIEW__EXPAND,
216                                                                                          audit_name,
217                                                                                          abort_on_violation);
218                         break;
219
220                 default:
221                         /* nothing to be checked */
222                         break;
223         }
224         pfree(audit_name);
225
226         /*
227          * Only columns owned by relations shall be checked
228          */
229         if (relkind != RELKIND_RELATION)
230                 return true;
231
232         /*
233          * Check permissions on the columns
234          */
235         selected = fixup_whole_row_references(relOid, selected);
236         modified = fixup_whole_row_references(relOid, modified);
237         columns = bms_union(selected, modified);
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, modified))
247                 {
248                         if (required & SEPG_DB_TABLE__UPDATE)
249                                 column_perms |= SEPG_DB_COLUMN__UPDATE;
250                         if (required & SEPG_DB_TABLE__INSERT)
251                                 column_perms |= SEPG_DB_COLUMN__INSERT;
252                 }
253                 if (column_perms == 0)
254                         continue;
255
256                 /* obtain column's permission */
257                 attnum = index + FirstLowInvalidHeapAttributeNumber;
258
259                 object.classId = RelationRelationId;
260                 object.objectId = relOid;
261                 object.objectSubId = attnum;
262                 audit_name = getObjectDescription(&object);
263
264                 result = sepgsql_avc_check_perms(&object,
265                                                                                  SEPG_CLASS_DB_COLUMN,
266                                                                                  column_perms,
267                                                                                  audit_name,
268                                                                                  abort_on_violation);
269                 pfree(audit_name);
270
271                 if (!result)
272                         return result;
273         }
274         return true;
275 }
276
277 /*
278  * sepgsql_dml_privileges
279  *
280  * Entrypoint of the DML permission checks
281  */
282 bool
283 sepgsql_dml_privileges(List *rangeTabls, bool abort_on_violation)
284 {
285         ListCell   *lr;
286
287         foreach(lr, rangeTabls)
288         {
289                 RangeTblEntry *rte = lfirst(lr);
290                 uint32          required = 0;
291                 List       *tableIds;
292                 ListCell   *li;
293
294                 /*
295                  * Only regular relations shall be checked
296                  */
297                 if (rte->rtekind != RTE_RELATION)
298                         continue;
299
300                 /*
301                  * Find out required permissions
302                  */
303                 if (rte->requiredPerms & ACL_SELECT)
304                         required |= SEPG_DB_TABLE__SELECT;
305                 if (rte->requiredPerms & ACL_INSERT)
306                         required |= SEPG_DB_TABLE__INSERT;
307                 if (rte->requiredPerms & ACL_UPDATE)
308                 {
309                         if (!bms_is_empty(rte->modifiedCols))
310                                 required |= SEPG_DB_TABLE__UPDATE;
311                         else
312                                 required |= SEPG_DB_TABLE__LOCK;
313                 }
314                 if (rte->requiredPerms & ACL_DELETE)
315                         required |= SEPG_DB_TABLE__DELETE;
316
317                 /*
318                  * Skip, if nothing to be checked
319                  */
320                 if (required == 0)
321                         continue;
322
323                 /*
324                  * If this RangeTblEntry is also supposed to reference inherited
325                  * tables, we need to check security label of the child tables. So, we
326                  * expand rte->relid into list of OIDs of inheritance hierarchy, then
327                  * checker routine will be invoked for each relations.
328                  */
329                 if (!rte->inh)
330                         tableIds = list_make1_oid(rte->relid);
331                 else
332                         tableIds = find_all_inheritors(rte->relid, NoLock, NULL);
333
334                 foreach(li, tableIds)
335                 {
336                         Oid                     tableOid = lfirst_oid(li);
337                         Bitmapset  *selectedCols;
338                         Bitmapset  *modifiedCols;
339
340                         /*
341                          * child table has different attribute numbers, so we need to fix
342                          * up them.
343                          */
344                         selectedCols = fixup_inherited_columns(rte->relid, tableOid,
345                                                                                                    rte->selectedCols);
346                         modifiedCols = fixup_inherited_columns(rte->relid, tableOid,
347                                                                                                    rte->modifiedCols);
348
349                         /*
350                          * check permissions on individual tables
351                          */
352                         if (!check_relation_privileges(tableOid,
353                                                                                    selectedCols,
354                                                                                    modifiedCols,
355                                                                                    required, abort_on_violation))
356                                 return false;
357                 }
358                 list_free(tableIds);
359         }
360         return true;
361 }