1 /* -------------------------------------------------------------------------
2 *
3 * contrib/sepgsql/relation.c
4 *
5 * Routines corresponding to relation/attribute objects
6 *
7 * Copyright (c) 2010-2016, PostgreSQL Global Development Group
8 *
9 * -------------------------------------------------------------------------
10 */
11 #include "postgres.h"
12
13 #include "access/genam.h"
14 #include "access/heapam.h"
15 #include "access/htup_details.h"
16 #include "access/sysattr.h"
17 #include "catalog/indexing.h"
18 #include "catalog/dependency.h"
19 #include "catalog/pg_attribute.h"
20 #include "catalog/pg_class.h"
21 #include "catalog/pg_namespace.h"
22 #include "commands/seclabel.h"
23 #include "lib/stringinfo.h"
24 #include "utils/builtins.h"
25 #include "utils/fmgroids.h"
26 #include "utils/catcache.h"
27 #include "utils/lsyscache.h"
28 #include "utils/rel.h"
29 #include "utils/syscache.h"
30 #include "utils/tqual.h"
31
32 #include "sepgsql.h"
33
34 static void sepgsql_index_modify(Oid indexOid);
35
36 /*
37 * sepgsql_attribute_post_create
38 *
39 * This routine assigns a default security label on a newly defined
40 * column, using ALTER TABLE ... ADD COLUMN.
41 * Note that this routine is not invoked in the case of CREATE TABLE,
42 * although it also defines columns in addition to table.
43 */
44 void
sepgsql_attribute_post_create(Oid relOid,AttrNumber attnum)45 sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
46 {
47 Relation rel;
48 ScanKeyData skey[2];
49 SysScanDesc sscan;
50 HeapTuple tuple;
51 char *scontext;
52 char *tcontext;
53 char *ncontext;
54 ObjectAddress object;
55 Form_pg_attribute attForm;
56 StringInfoData audit_name;
57
58 /*
59 * Only attributes within regular relation have individual security
60 * labels.
61 */
62 if (get_rel_relkind(relOid) != RELKIND_RELATION)
63 return;
64
65 /*
66 * Compute a default security label of the new column underlying the
67 * specified relation, and check permission to create it.
68 */
69 rel = heap_open(AttributeRelationId, AccessShareLock);
70
71 ScanKeyInit(&skey[0],
72 Anum_pg_attribute_attrelid,
73 BTEqualStrategyNumber, F_OIDEQ,
74 ObjectIdGetDatum(relOid));
75 ScanKeyInit(&skey[1],
76 Anum_pg_attribute_attnum,
77 BTEqualStrategyNumber, F_INT2EQ,
78 Int16GetDatum(attnum));
79
80 sscan = systable_beginscan(rel, AttributeRelidNumIndexId, true,
81 SnapshotSelf, 2, &skey[0]);
82
83 tuple = systable_getnext(sscan);
84 if (!HeapTupleIsValid(tuple))
85 elog(ERROR, "catalog lookup failed for column %d of relation %u",
86 attnum, relOid);
87
88 attForm = (Form_pg_attribute) GETSTRUCT(tuple);
89
90 scontext = sepgsql_get_client_label();
91 tcontext = sepgsql_get_label(RelationRelationId, relOid, 0);
92 ncontext = sepgsql_compute_create(scontext, tcontext,
93 SEPG_CLASS_DB_COLUMN,
94 NameStr(attForm->attname));
95
96 /*
97 * check db_column:{create} permission
98 */
99 object.classId = RelationRelationId;
100 object.objectId = relOid;
101 object.objectSubId = 0;
102
103 initStringInfo(&audit_name);
104 appendStringInfo(&audit_name, "%s.%s",
105 getObjectIdentity(&object),
106 quote_identifier(NameStr(attForm->attname)));
107 sepgsql_avc_check_perms_label(ncontext,
108 SEPG_CLASS_DB_COLUMN,
109 SEPG_DB_COLUMN__CREATE,
110 audit_name.data,
111 true);
112
113 /*
114 * Assign the default security label on a new procedure
115 */
116 object.classId = RelationRelationId;
117 object.objectId = relOid;
118 object.objectSubId = attnum;
119 SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
120
121 systable_endscan(sscan);
122 heap_close(rel, AccessShareLock);
123
124 pfree(tcontext);
125 pfree(ncontext);
126 }
127
128 /*
129 * sepgsql_attribute_drop
130 *
131 * It checks privileges to drop the supplied column.
132 */
133 void
sepgsql_attribute_drop(Oid relOid,AttrNumber attnum)134 sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
135 {
136 ObjectAddress object;
137 char *audit_name;
138
139 if (get_rel_relkind(relOid) != RELKIND_RELATION)
140 return;
141
142 /*
143 * check db_column:{drop} permission
144 */
145 object.classId = RelationRelationId;
146 object.objectId = relOid;
147 object.objectSubId = attnum;
148 audit_name = getObjectIdentity(&object);
149
150 sepgsql_avc_check_perms(&object,
151 SEPG_CLASS_DB_COLUMN,
152 SEPG_DB_COLUMN__DROP,
153 audit_name,
154 true);
155 pfree(audit_name);
156 }
157
158 /*
159 * sepgsql_attribute_relabel
160 *
161 * It checks privileges to relabel the supplied column
162 * by the `seclabel'.
163 */
164 void
sepgsql_attribute_relabel(Oid relOid,AttrNumber attnum,const char * seclabel)165 sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
166 const char *seclabel)
167 {
168 ObjectAddress object;
169 char *audit_name;
170
171 if (get_rel_relkind(relOid) != RELKIND_RELATION)
172 ereport(ERROR,
173 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
174 errmsg("cannot set security label on non-regular columns")));
175
176 object.classId = RelationRelationId;
177 object.objectId = relOid;
178 object.objectSubId = attnum;
179 audit_name = getObjectIdentity(&object);
180
181 /*
182 * check db_column:{setattr relabelfrom} permission
183 */
184 sepgsql_avc_check_perms(&object,
185 SEPG_CLASS_DB_COLUMN,
186 SEPG_DB_COLUMN__SETATTR |
187 SEPG_DB_COLUMN__RELABELFROM,
188 audit_name,
189 true);
190
191 /*
192 * check db_column:{relabelto} permission
193 */
194 sepgsql_avc_check_perms_label(seclabel,
195 SEPG_CLASS_DB_COLUMN,
196 SEPG_DB_PROCEDURE__RELABELTO,
197 audit_name,
198 true);
199 pfree(audit_name);
200 }
201
202 /*
203 * sepgsql_attribute_setattr
204 *
205 * It checks privileges to alter the supplied column.
206 */
207 void
sepgsql_attribute_setattr(Oid relOid,AttrNumber attnum)208 sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
209 {
210 ObjectAddress object;
211 char *audit_name;
212
213 if (get_rel_relkind(relOid) != RELKIND_RELATION)
214 return;
215
216 /*
217 * check db_column:{setattr} permission
218 */
219 object.classId = RelationRelationId;
220 object.objectId = relOid;
221 object.objectSubId = attnum;
222 audit_name = getObjectIdentity(&object);
223
224 sepgsql_avc_check_perms(&object,
225 SEPG_CLASS_DB_COLUMN,
226 SEPG_DB_COLUMN__SETATTR,
227 audit_name,
228 true);
229 pfree(audit_name);
230 }
231
232 /*
233 * sepgsql_relation_post_create
234 *
235 * The post creation hook of relation/attribute
236 */
237 void
sepgsql_relation_post_create(Oid relOid)238 sepgsql_relation_post_create(Oid relOid)
239 {
240 Relation rel;
241 ScanKeyData skey;
242 SysScanDesc sscan;
243 HeapTuple tuple;
244 Form_pg_class classForm;
245 ObjectAddress object;
246 uint16 tclass;
247 char *scontext; /* subject */
248 char *tcontext; /* schema */
249 char *rcontext; /* relation */
250 char *ccontext; /* column */
251 char *nsp_name;
252 StringInfoData audit_name;
253
254 /*
255 * Fetch catalog record of the new relation. Because pg_class entry is not
256 * visible right now, we need to scan the catalog using SnapshotSelf.
257 */
258 rel = heap_open(RelationRelationId, AccessShareLock);
259
260 ScanKeyInit(&skey,
261 ObjectIdAttributeNumber,
262 BTEqualStrategyNumber, F_OIDEQ,
263 ObjectIdGetDatum(relOid));
264
265 sscan = systable_beginscan(rel, ClassOidIndexId, true,
266 SnapshotSelf, 1, &skey);
267
268 tuple = systable_getnext(sscan);
269 if (!HeapTupleIsValid(tuple))
270 elog(ERROR, "catalog lookup failed for relation %u", relOid);
271
272 classForm = (Form_pg_class) GETSTRUCT(tuple);
273
274 /* ignore indexes on toast tables */
275 if (classForm->relkind == RELKIND_INDEX &&
276 classForm->relnamespace == PG_TOAST_NAMESPACE)
277 goto out;
278
279 /*
280 * check db_schema:{add_name} permission of the namespace
281 */
282 object.classId = NamespaceRelationId;
283 object.objectId = classForm->relnamespace;
284 object.objectSubId = 0;
285 sepgsql_avc_check_perms(&object,
286 SEPG_CLASS_DB_SCHEMA,
287 SEPG_DB_SCHEMA__ADD_NAME,
288 getObjectIdentity(&object),
289 true);
290
291 switch (classForm->relkind)
292 {
293 case RELKIND_RELATION:
294 tclass = SEPG_CLASS_DB_TABLE;
295 break;
296 case RELKIND_SEQUENCE:
297 tclass = SEPG_CLASS_DB_SEQUENCE;
298 break;
299 case RELKIND_VIEW:
300 tclass = SEPG_CLASS_DB_VIEW;
301 break;
302 case RELKIND_INDEX:
303 /* deal with indexes specially; no need for tclass */
304 sepgsql_index_modify(relOid);
305 goto out;
306 default:
307 /* ignore other relkinds */
308 goto out;
309 }
310
311 /*
312 * Compute a default security label when we create a new relation object
313 * under the specified namespace.
314 */
315 scontext = sepgsql_get_client_label();
316 tcontext = sepgsql_get_label(NamespaceRelationId,
317 classForm->relnamespace, 0);
318 rcontext = sepgsql_compute_create(scontext, tcontext, tclass,
319 NameStr(classForm->relname));
320
321 /*
322 * check db_xxx:{create} permission
323 */
324 nsp_name = get_namespace_name(classForm->relnamespace);
325 initStringInfo(&audit_name);
326 appendStringInfo(&audit_name, "%s.%s",
327 quote_identifier(nsp_name),
328 quote_identifier(NameStr(classForm->relname)));
329 sepgsql_avc_check_perms_label(rcontext,
330 tclass,
331 SEPG_DB_DATABASE__CREATE,
332 audit_name.data,
333 true);
334
335 /*
336 * Assign the default security label on the new relation
337 */
338 object.classId = RelationRelationId;
339 object.objectId = relOid;
340 object.objectSubId = 0;
341 SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext);
342
343 /*
344 * We also assigns a default security label on columns of the new regular
345 * tables.
346 */
347 if (classForm->relkind == RELKIND_RELATION)
348 {
349 Relation arel;
350 ScanKeyData akey;
351 SysScanDesc ascan;
352 HeapTuple atup;
353 Form_pg_attribute attForm;
354
355 arel = heap_open(AttributeRelationId, AccessShareLock);
356
357 ScanKeyInit(&akey,
358 Anum_pg_attribute_attrelid,
359 BTEqualStrategyNumber, F_OIDEQ,
360 ObjectIdGetDatum(relOid));
361
362 ascan = systable_beginscan(arel, AttributeRelidNumIndexId, true,
363 SnapshotSelf, 1, &akey);
364
365 while (HeapTupleIsValid(atup = systable_getnext(ascan)))
366 {
367 attForm = (Form_pg_attribute) GETSTRUCT(atup);
368
369 resetStringInfo(&audit_name);
370 appendStringInfo(&audit_name, "%s.%s.%s",
371 quote_identifier(nsp_name),
372 quote_identifier(NameStr(classForm->relname)),
373 quote_identifier(NameStr(attForm->attname)));
374
375 ccontext = sepgsql_compute_create(scontext,
376 rcontext,
377 SEPG_CLASS_DB_COLUMN,
378 NameStr(attForm->attname));
379
380 /*
381 * check db_column:{create} permission
382 */
383 sepgsql_avc_check_perms_label(ccontext,
384 SEPG_CLASS_DB_COLUMN,
385 SEPG_DB_COLUMN__CREATE,
386 audit_name.data,
387 true);
388
389 object.classId = RelationRelationId;
390 object.objectId = relOid;
391 object.objectSubId = attForm->attnum;
392 SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext);
393
394 pfree(ccontext);
395 }
396 systable_endscan(ascan);
397 heap_close(arel, AccessShareLock);
398 }
399 pfree(rcontext);
400
401 out:
402 systable_endscan(sscan);
403 heap_close(rel, AccessShareLock);
404 }
405
406 /*
407 * sepgsql_relation_drop
408 *
409 * It checks privileges to drop the supplied relation.
410 */
411 void
sepgsql_relation_drop(Oid relOid)412 sepgsql_relation_drop(Oid relOid)
413 {
414 ObjectAddress object;
415 char *audit_name;
416 uint16_t tclass;
417 char relkind;
418
419 relkind = get_rel_relkind(relOid);
420 switch (relkind)
421 {
422 case RELKIND_RELATION:
423 tclass = SEPG_CLASS_DB_TABLE;
424 break;
425 case RELKIND_SEQUENCE:
426 tclass = SEPG_CLASS_DB_SEQUENCE;
427 break;
428 case RELKIND_VIEW:
429 tclass = SEPG_CLASS_DB_VIEW;
430 break;
431 case RELKIND_INDEX:
432 /* ignore indexes on toast tables */
433 if (get_rel_namespace(relOid) == PG_TOAST_NAMESPACE)
434 return;
435 /* other indexes are handled specially below; no need for tclass */
436 break;
437 default:
438 /* ignore other relkinds */
439 return;
440 }
441
442 /*
443 * check db_schema:{remove_name} permission
444 */
445 object.classId = NamespaceRelationId;
446 object.objectId = get_rel_namespace(relOid);
447 object.objectSubId = 0;
448 audit_name = getObjectIdentity(&object);
449
450 sepgsql_avc_check_perms(&object,
451 SEPG_CLASS_DB_SCHEMA,
452 SEPG_DB_SCHEMA__REMOVE_NAME,
453 audit_name,
454 true);
455 pfree(audit_name);
456
457 /* deal with indexes specially */
458 if (relkind == RELKIND_INDEX)
459 {
460 sepgsql_index_modify(relOid);
461 return;
462 }
463
464 /*
465 * check db_table/sequence/view:{drop} permission
466 */
467 object.classId = RelationRelationId;
468 object.objectId = relOid;
469 object.objectSubId = 0;
470 audit_name = getObjectIdentity(&object);
471
472 sepgsql_avc_check_perms(&object,
473 tclass,
474 SEPG_DB_TABLE__DROP,
475 audit_name,
476 true);
477 pfree(audit_name);
478
479 /*
480 * check db_column:{drop} permission
481 */
482 if (relkind == RELKIND_RELATION)
483 {
484 Form_pg_attribute attForm;
485 CatCList *attrList;
486 HeapTuple atttup;
487 int i;
488
489 attrList = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(relOid));
490 for (i = 0; i < attrList->n_members; i++)
491 {
492 atttup = &attrList->members[i]->tuple;
493 attForm = (Form_pg_attribute) GETSTRUCT(atttup);
494
495 if (attForm->attisdropped)
496 continue;
497
498 object.classId = RelationRelationId;
499 object.objectId = relOid;
500 object.objectSubId = attForm->attnum;
501 audit_name = getObjectIdentity(&object);
502
503 sepgsql_avc_check_perms(&object,
504 SEPG_CLASS_DB_COLUMN,
505 SEPG_DB_COLUMN__DROP,
506 audit_name,
507 true);
508 pfree(audit_name);
509 }
510 ReleaseCatCacheList(attrList);
511 }
512 }
513
514 /*
515 * sepgsql_relation_relabel
516 *
517 * It checks privileges to relabel the supplied relation by the `seclabel'.
518 */
519 void
sepgsql_relation_relabel(Oid relOid,const char * seclabel)520 sepgsql_relation_relabel(Oid relOid, const char *seclabel)
521 {
522 ObjectAddress object;
523 char *audit_name;
524 char relkind;
525 uint16_t tclass = 0;
526
527 relkind = get_rel_relkind(relOid);
528 if (relkind == RELKIND_RELATION)
529 tclass = SEPG_CLASS_DB_TABLE;
530 else if (relkind == RELKIND_SEQUENCE)
531 tclass = SEPG_CLASS_DB_SEQUENCE;
532 else if (relkind == RELKIND_VIEW)
533 tclass = SEPG_CLASS_DB_VIEW;
534 else
535 ereport(ERROR,
536 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
537 errmsg("cannot set security labels on relations except "
538 "for tables, sequences or views")));
539
540 object.classId = RelationRelationId;
541 object.objectId = relOid;
542 object.objectSubId = 0;
543 audit_name = getObjectIdentity(&object);
544
545 /*
546 * check db_xxx:{setattr relabelfrom} permission
547 */
548 sepgsql_avc_check_perms(&object,
549 tclass,
550 SEPG_DB_TABLE__SETATTR |
551 SEPG_DB_TABLE__RELABELFROM,
552 audit_name,
553 true);
554
555 /*
556 * check db_xxx:{relabelto} permission
557 */
558 sepgsql_avc_check_perms_label(seclabel,
559 tclass,
560 SEPG_DB_TABLE__RELABELTO,
561 audit_name,
562 true);
563 pfree(audit_name);
564 }
565
566 /*
567 * sepgsql_relation_setattr
568 *
569 * It checks privileges to set attribute of the supplied relation
570 */
571 void
sepgsql_relation_setattr(Oid relOid)572 sepgsql_relation_setattr(Oid relOid)
573 {
574 Relation rel;
575 ScanKeyData skey;
576 SysScanDesc sscan;
577 HeapTuple oldtup;
578 HeapTuple newtup;
579 Form_pg_class oldform;
580 Form_pg_class newform;
581 ObjectAddress object;
582 char *audit_name;
583 uint16_t tclass;
584
585 switch (get_rel_relkind(relOid))
586 {
587 case RELKIND_RELATION:
588 tclass = SEPG_CLASS_DB_TABLE;
589 break;
590 case RELKIND_SEQUENCE:
591 tclass = SEPG_CLASS_DB_SEQUENCE;
592 break;
593 case RELKIND_VIEW:
594 tclass = SEPG_CLASS_DB_VIEW;
595 break;
596 case RELKIND_INDEX:
597 /* deal with indexes specially */
598 sepgsql_index_modify(relOid);
599 return;
600 default:
601 /* other relkinds don't need additional work */
602 return;
603 }
604
605 /*
606 * Fetch newer catalog
607 */
608 rel = heap_open(RelationRelationId, AccessShareLock);
609
610 ScanKeyInit(&skey,
611 ObjectIdAttributeNumber,
612 BTEqualStrategyNumber, F_OIDEQ,
613 ObjectIdGetDatum(relOid));
614
615 sscan = systable_beginscan(rel, ClassOidIndexId, true,
616 SnapshotSelf, 1, &skey);
617
618 newtup = systable_getnext(sscan);
619 if (!HeapTupleIsValid(newtup))
620 elog(ERROR, "catalog lookup failed for relation %u", relOid);
621 newform = (Form_pg_class) GETSTRUCT(newtup);
622
623 /*
624 * Fetch older catalog
625 */
626 oldtup = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
627 if (!HeapTupleIsValid(oldtup))
628 elog(ERROR, "cache lookup failed for relation %u", relOid);
629 oldform = (Form_pg_class) GETSTRUCT(oldtup);
630
631 /*
632 * Does this ALTER command takes operation to namespace?
633 */
634 if (newform->relnamespace != oldform->relnamespace)
635 {
636 sepgsql_schema_remove_name(oldform->relnamespace);
637 sepgsql_schema_add_name(newform->relnamespace);
638 }
639 if (strcmp(NameStr(newform->relname), NameStr(oldform->relname)) != 0)
640 sepgsql_schema_rename(oldform->relnamespace);
641
642 /*
643 * XXX - In the future version, db_tuple:{use} of system catalog entry
644 * shall be checked, if tablespace configuration is changed.
645 */
646
647 /*
648 * check db_xxx:{setattr} permission
649 */
650 object.classId = RelationRelationId;
651 object.objectId = relOid;
652 object.objectSubId = 0;
653 audit_name = getObjectIdentity(&object);
654
655 sepgsql_avc_check_perms(&object,
656 tclass,
657 SEPG_DB_TABLE__SETATTR,
658 audit_name,
659 true);
660 pfree(audit_name);
661
662 ReleaseSysCache(oldtup);
663 systable_endscan(sscan);
664 heap_close(rel, AccessShareLock);
665 }
666
667 /*
668 * sepgsql_relation_setattr_extra
669 *
670 * It checks permission of the relation being referenced by extra attributes,
671 * such as pg_index entries. Like core PostgreSQL, sepgsql also does not deal
672 * with such entries as individual "objects", thus, modification of these
673 * entries shall be considered as setting an attribute of the underlying
674 * relation.
675 */
676 static void
sepgsql_relation_setattr_extra(Relation catalog,Oid catindex_id,Oid extra_oid,AttrNumber anum_relation_id,AttrNumber anum_extra_id)677 sepgsql_relation_setattr_extra(Relation catalog,
678 Oid catindex_id,
679 Oid extra_oid,
680 AttrNumber anum_relation_id,
681 AttrNumber anum_extra_id)
682 {
683 ScanKeyData skey;
684 SysScanDesc sscan;
685 HeapTuple tuple;
686 Datum datum;
687 bool isnull;
688
689 ScanKeyInit(&skey, anum_extra_id,
690 BTEqualStrategyNumber, F_OIDEQ,
691 ObjectIdGetDatum(extra_oid));
692
693 sscan = systable_beginscan(catalog, catindex_id, true,
694 SnapshotSelf, 1, &skey);
695 tuple = systable_getnext(sscan);
696 if (!HeapTupleIsValid(tuple))
697 elog(ERROR, "catalog lookup failed for object %u in catalog \"%s\"",
698 extra_oid, RelationGetRelationName(catalog));
699
700 datum = heap_getattr(tuple, anum_relation_id,
701 RelationGetDescr(catalog), &isnull);
702 Assert(!isnull);
703
704 sepgsql_relation_setattr(DatumGetObjectId(datum));
705
706 systable_endscan(sscan);
707 }
708
709 /*
710 * sepgsql_index_modify
711 * Handle index create, update, drop
712 *
713 * Unlike other relation kinds, indexes do not have their own security labels,
714 * so instead of doing checks directly, treat them as extra attributes of their
715 * owning tables; so check 'setattr' permissions on the table.
716 */
717 static void
sepgsql_index_modify(Oid indexOid)718 sepgsql_index_modify(Oid indexOid)
719 {
720 Relation catalog = heap_open(IndexRelationId, AccessShareLock);
721
722 /* check db_table:{setattr} permission of the table being indexed */
723 sepgsql_relation_setattr_extra(catalog,
724 IndexRelidIndexId,
725 indexOid,
726 Anum_pg_index_indrelid,
727 Anum_pg_index_indexrelid);
728 heap_close(catalog, AccessShareLock);
729 }
730