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