Update copyright for 2022
[pgsql.git] / contrib / sepgsql / relation.c
blob8767988c4d300d0e42960561bcd493a010a6aaab
1 /* -------------------------------------------------------------------------
3 * contrib/sepgsql/relation.c
5 * Routines corresponding to relation/attribute objects
7 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
9 * -------------------------------------------------------------------------
11 #include "postgres.h"
13 #include "access/genam.h"
14 #include "access/htup_details.h"
15 #include "access/sysattr.h"
16 #include "access/table.h"
17 #include "catalog/dependency.h"
18 #include "catalog/pg_attribute.h"
19 #include "catalog/pg_class.h"
20 #include "catalog/pg_namespace.h"
21 #include "commands/seclabel.h"
22 #include "lib/stringinfo.h"
23 #include "sepgsql.h"
24 #include "utils/builtins.h"
25 #include "utils/catcache.h"
26 #include "utils/fmgroids.h"
27 #include "utils/lsyscache.h"
28 #include "utils/rel.h"
29 #include "utils/snapmgr.h"
30 #include "utils/syscache.h"
32 static void sepgsql_index_modify(Oid indexOid);
35 * sepgsql_attribute_post_create
37 * This routine assigns a default security label on a newly defined
38 * column, using ALTER TABLE ... ADD COLUMN.
39 * Note that this routine is not invoked in the case of CREATE TABLE,
40 * although it also defines columns in addition to table.
42 void
43 sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum)
45 Relation rel;
46 ScanKeyData skey[2];
47 SysScanDesc sscan;
48 HeapTuple tuple;
49 char *scontext;
50 char *tcontext;
51 char *ncontext;
52 ObjectAddress object;
53 Form_pg_attribute attForm;
54 StringInfoData audit_name;
55 char relkind = get_rel_relkind(relOid);
58 * Only attributes within regular relations or partition relations have
59 * individual security labels.
61 if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
62 return;
65 * Compute a default security label of the new column underlying the
66 * specified relation, and check permission to create it.
68 rel = table_open(AttributeRelationId, AccessShareLock);
70 ScanKeyInit(&skey[0],
71 Anum_pg_attribute_attrelid,
72 BTEqualStrategyNumber, F_OIDEQ,
73 ObjectIdGetDatum(relOid));
74 ScanKeyInit(&skey[1],
75 Anum_pg_attribute_attnum,
76 BTEqualStrategyNumber, F_INT2EQ,
77 Int16GetDatum(attnum));
79 sscan = systable_beginscan(rel, AttributeRelidNumIndexId, true,
80 SnapshotSelf, 2, &skey[0]);
82 tuple = systable_getnext(sscan);
83 if (!HeapTupleIsValid(tuple))
84 elog(ERROR, "could not find tuple for column %d of relation %u",
85 attnum, relOid);
87 attForm = (Form_pg_attribute) GETSTRUCT(tuple);
89 scontext = sepgsql_get_client_label();
90 tcontext = sepgsql_get_label(RelationRelationId, relOid, 0);
91 ncontext = sepgsql_compute_create(scontext, tcontext,
92 SEPG_CLASS_DB_COLUMN,
93 NameStr(attForm->attname));
96 * check db_column:{create} permission
98 object.classId = RelationRelationId;
99 object.objectId = relOid;
100 object.objectSubId = 0;
102 initStringInfo(&audit_name);
103 appendStringInfo(&audit_name, "%s.%s",
104 getObjectIdentity(&object, false),
105 quote_identifier(NameStr(attForm->attname)));
106 sepgsql_avc_check_perms_label(ncontext,
107 SEPG_CLASS_DB_COLUMN,
108 SEPG_DB_COLUMN__CREATE,
109 audit_name.data,
110 true);
113 * Assign the default security label on a new procedure
115 object.classId = RelationRelationId;
116 object.objectId = relOid;
117 object.objectSubId = attnum;
118 SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext);
120 systable_endscan(sscan);
121 table_close(rel, AccessShareLock);
123 pfree(tcontext);
124 pfree(ncontext);
128 * sepgsql_attribute_drop
130 * It checks privileges to drop the supplied column.
132 void
133 sepgsql_attribute_drop(Oid relOid, AttrNumber attnum)
135 ObjectAddress object;
136 char *audit_name;
137 char relkind = get_rel_relkind(relOid);
139 if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
140 return;
143 * check db_column:{drop} permission
145 object.classId = RelationRelationId;
146 object.objectId = relOid;
147 object.objectSubId = attnum;
148 audit_name = getObjectIdentity(&object, false);
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);
159 * sepgsql_attribute_relabel
161 * It checks privileges to relabel the supplied column
162 * by the `seclabel'.
164 void
165 sepgsql_attribute_relabel(Oid relOid, AttrNumber attnum,
166 const char *seclabel)
168 ObjectAddress object;
169 char *audit_name;
170 char relkind = get_rel_relkind(relOid);
172 if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
173 ereport(ERROR,
174 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
175 errmsg("cannot set security label on non-regular columns")));
177 object.classId = RelationRelationId;
178 object.objectId = relOid;
179 object.objectSubId = attnum;
180 audit_name = getObjectIdentity(&object, false);
183 * check db_column:{setattr relabelfrom} permission
185 sepgsql_avc_check_perms(&object,
186 SEPG_CLASS_DB_COLUMN,
187 SEPG_DB_COLUMN__SETATTR |
188 SEPG_DB_COLUMN__RELABELFROM,
189 audit_name,
190 true);
193 * check db_column:{relabelto} permission
195 sepgsql_avc_check_perms_label(seclabel,
196 SEPG_CLASS_DB_COLUMN,
197 SEPG_DB_PROCEDURE__RELABELTO,
198 audit_name,
199 true);
200 pfree(audit_name);
204 * sepgsql_attribute_setattr
206 * It checks privileges to alter the supplied column.
208 void
209 sepgsql_attribute_setattr(Oid relOid, AttrNumber attnum)
211 ObjectAddress object;
212 char *audit_name;
213 char relkind = get_rel_relkind(relOid);
215 if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
216 return;
219 * check db_column:{setattr} permission
221 object.classId = RelationRelationId;
222 object.objectId = relOid;
223 object.objectSubId = attnum;
224 audit_name = getObjectIdentity(&object, false);
226 sepgsql_avc_check_perms(&object,
227 SEPG_CLASS_DB_COLUMN,
228 SEPG_DB_COLUMN__SETATTR,
229 audit_name,
230 true);
231 pfree(audit_name);
235 * sepgsql_relation_post_create
237 * The post creation hook of relation/attribute
239 void
240 sepgsql_relation_post_create(Oid relOid)
242 Relation rel;
243 ScanKeyData skey;
244 SysScanDesc sscan;
245 HeapTuple tuple;
246 Form_pg_class classForm;
247 ObjectAddress object;
248 uint16_t tclass;
249 char *scontext; /* subject */
250 char *tcontext; /* schema */
251 char *rcontext; /* relation */
252 char *ccontext; /* column */
253 char *nsp_name;
254 StringInfoData audit_name;
257 * Fetch catalog record of the new relation. Because pg_class entry is not
258 * visible right now, we need to scan the catalog using SnapshotSelf.
260 rel = table_open(RelationRelationId, AccessShareLock);
262 ScanKeyInit(&skey,
263 Anum_pg_class_oid,
264 BTEqualStrategyNumber, F_OIDEQ,
265 ObjectIdGetDatum(relOid));
267 sscan = systable_beginscan(rel, ClassOidIndexId, true,
268 SnapshotSelf, 1, &skey);
270 tuple = systable_getnext(sscan);
271 if (!HeapTupleIsValid(tuple))
272 elog(ERROR, "could not find tuple for relation %u", relOid);
274 classForm = (Form_pg_class) GETSTRUCT(tuple);
276 /* ignore indexes on toast tables */
277 if (classForm->relkind == RELKIND_INDEX &&
278 classForm->relnamespace == PG_TOAST_NAMESPACE)
279 goto out;
282 * check db_schema:{add_name} permission of the namespace
284 object.classId = NamespaceRelationId;
285 object.objectId = classForm->relnamespace;
286 object.objectSubId = 0;
287 sepgsql_avc_check_perms(&object,
288 SEPG_CLASS_DB_SCHEMA,
289 SEPG_DB_SCHEMA__ADD_NAME,
290 getObjectIdentity(&object, false),
291 true);
293 switch (classForm->relkind)
295 case RELKIND_RELATION:
296 case RELKIND_PARTITIONED_TABLE:
297 tclass = SEPG_CLASS_DB_TABLE;
298 break;
299 case RELKIND_SEQUENCE:
300 tclass = SEPG_CLASS_DB_SEQUENCE;
301 break;
302 case RELKIND_VIEW:
303 tclass = SEPG_CLASS_DB_VIEW;
304 break;
305 case RELKIND_INDEX:
306 /* deal with indexes specially; no need for tclass */
307 sepgsql_index_modify(relOid);
308 goto out;
309 default:
310 /* ignore other relkinds */
311 goto out;
315 * Compute a default security label when we create a new relation object
316 * under the specified namespace.
318 scontext = sepgsql_get_client_label();
319 tcontext = sepgsql_get_label(NamespaceRelationId,
320 classForm->relnamespace, 0);
321 rcontext = sepgsql_compute_create(scontext, tcontext, tclass,
322 NameStr(classForm->relname));
325 * check db_xxx:{create} permission
327 nsp_name = get_namespace_name(classForm->relnamespace);
328 initStringInfo(&audit_name);
329 appendStringInfo(&audit_name, "%s.%s",
330 quote_identifier(nsp_name),
331 quote_identifier(NameStr(classForm->relname)));
332 sepgsql_avc_check_perms_label(rcontext,
333 tclass,
334 SEPG_DB_DATABASE__CREATE,
335 audit_name.data,
336 true);
339 * Assign the default security label on the new regular or partitioned
340 * relation.
342 object.classId = RelationRelationId;
343 object.objectId = relOid;
344 object.objectSubId = 0;
345 SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, rcontext);
348 * We also assign a default security label on columns of a new table.
350 if (classForm->relkind == RELKIND_RELATION ||
351 classForm->relkind == RELKIND_PARTITIONED_TABLE)
353 Relation arel;
354 ScanKeyData akey;
355 SysScanDesc ascan;
356 HeapTuple atup;
357 Form_pg_attribute attForm;
359 arel = table_open(AttributeRelationId, AccessShareLock);
361 ScanKeyInit(&akey,
362 Anum_pg_attribute_attrelid,
363 BTEqualStrategyNumber, F_OIDEQ,
364 ObjectIdGetDatum(relOid));
366 ascan = systable_beginscan(arel, AttributeRelidNumIndexId, true,
367 SnapshotSelf, 1, &akey);
369 while (HeapTupleIsValid(atup = systable_getnext(ascan)))
371 attForm = (Form_pg_attribute) GETSTRUCT(atup);
373 resetStringInfo(&audit_name);
374 appendStringInfo(&audit_name, "%s.%s.%s",
375 quote_identifier(nsp_name),
376 quote_identifier(NameStr(classForm->relname)),
377 quote_identifier(NameStr(attForm->attname)));
379 ccontext = sepgsql_compute_create(scontext,
380 rcontext,
381 SEPG_CLASS_DB_COLUMN,
382 NameStr(attForm->attname));
385 * check db_column:{create} permission
387 sepgsql_avc_check_perms_label(ccontext,
388 SEPG_CLASS_DB_COLUMN,
389 SEPG_DB_COLUMN__CREATE,
390 audit_name.data,
391 true);
393 object.classId = RelationRelationId;
394 object.objectId = relOid;
395 object.objectSubId = attForm->attnum;
396 SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext);
398 pfree(ccontext);
400 systable_endscan(ascan);
401 table_close(arel, AccessShareLock);
403 pfree(rcontext);
405 out:
406 systable_endscan(sscan);
407 table_close(rel, AccessShareLock);
411 * sepgsql_relation_drop
413 * It checks privileges to drop the supplied relation.
415 void
416 sepgsql_relation_drop(Oid relOid)
418 ObjectAddress object;
419 char *audit_name;
420 uint16_t tclass = 0;
421 char relkind = get_rel_relkind(relOid);
423 switch (relkind)
425 case RELKIND_RELATION:
426 case RELKIND_PARTITIONED_TABLE:
427 tclass = SEPG_CLASS_DB_TABLE;
428 break;
429 case RELKIND_SEQUENCE:
430 tclass = SEPG_CLASS_DB_SEQUENCE;
431 break;
432 case RELKIND_VIEW:
433 tclass = SEPG_CLASS_DB_VIEW;
434 break;
435 case RELKIND_INDEX:
436 /* ignore indexes on toast tables */
437 if (get_rel_namespace(relOid) == PG_TOAST_NAMESPACE)
438 return;
439 /* other indexes are handled specially below; no need for tclass */
440 break;
441 default:
442 /* ignore other relkinds */
443 return;
447 * check db_schema:{remove_name} permission
449 object.classId = NamespaceRelationId;
450 object.objectId = get_rel_namespace(relOid);
451 object.objectSubId = 0;
452 audit_name = getObjectIdentity(&object, false);
454 sepgsql_avc_check_perms(&object,
455 SEPG_CLASS_DB_SCHEMA,
456 SEPG_DB_SCHEMA__REMOVE_NAME,
457 audit_name,
458 true);
459 pfree(audit_name);
461 /* deal with indexes specially */
462 if (relkind == RELKIND_INDEX)
464 sepgsql_index_modify(relOid);
465 return;
469 * check db_table/sequence/view:{drop} permission
471 object.classId = RelationRelationId;
472 object.objectId = relOid;
473 object.objectSubId = 0;
474 audit_name = getObjectIdentity(&object, false);
476 sepgsql_avc_check_perms(&object,
477 tclass,
478 SEPG_DB_TABLE__DROP,
479 audit_name,
480 true);
481 pfree(audit_name);
484 * check db_column:{drop} permission
486 if (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)
488 Form_pg_attribute attForm;
489 CatCList *attrList;
490 HeapTuple atttup;
491 int i;
493 attrList = SearchSysCacheList1(ATTNUM, ObjectIdGetDatum(relOid));
494 for (i = 0; i < attrList->n_members; i++)
496 atttup = &attrList->members[i]->tuple;
497 attForm = (Form_pg_attribute) GETSTRUCT(atttup);
499 if (attForm->attisdropped)
500 continue;
502 object.classId = RelationRelationId;
503 object.objectId = relOid;
504 object.objectSubId = attForm->attnum;
505 audit_name = getObjectIdentity(&object, false);
507 sepgsql_avc_check_perms(&object,
508 SEPG_CLASS_DB_COLUMN,
509 SEPG_DB_COLUMN__DROP,
510 audit_name,
511 true);
512 pfree(audit_name);
514 ReleaseCatCacheList(attrList);
519 * sepgsql_relation_truncate
521 * Check privileges to TRUNCATE the supplied relation.
523 void
524 sepgsql_relation_truncate(Oid relOid)
526 ObjectAddress object;
527 char *audit_name;
528 uint16_t tclass = 0;
529 char relkind = get_rel_relkind(relOid);
531 switch (relkind)
533 case RELKIND_RELATION:
534 case RELKIND_PARTITIONED_TABLE:
535 tclass = SEPG_CLASS_DB_TABLE;
536 break;
537 default:
538 /* ignore other relkinds */
539 return;
543 * check db_table:{truncate} permission
545 object.classId = RelationRelationId;
546 object.objectId = relOid;
547 object.objectSubId = 0;
548 audit_name = getObjectIdentity(&object, false);
550 sepgsql_avc_check_perms(&object,
551 tclass,
552 SEPG_DB_TABLE__TRUNCATE,
553 audit_name,
554 true);
555 pfree(audit_name);
559 * sepgsql_relation_relabel
561 * It checks privileges to relabel the supplied relation by the `seclabel'.
563 void
564 sepgsql_relation_relabel(Oid relOid, const char *seclabel)
566 ObjectAddress object;
567 char *audit_name;
568 char relkind = get_rel_relkind(relOid);
569 uint16_t tclass = 0;
571 if (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)
572 tclass = SEPG_CLASS_DB_TABLE;
573 else if (relkind == RELKIND_SEQUENCE)
574 tclass = SEPG_CLASS_DB_SEQUENCE;
575 else if (relkind == RELKIND_VIEW)
576 tclass = SEPG_CLASS_DB_VIEW;
577 else
578 ereport(ERROR,
579 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
580 errmsg("cannot set security labels on relations except "
581 "for tables, sequences or views")));
583 object.classId = RelationRelationId;
584 object.objectId = relOid;
585 object.objectSubId = 0;
586 audit_name = getObjectIdentity(&object, false);
589 * check db_xxx:{setattr relabelfrom} permission
591 sepgsql_avc_check_perms(&object,
592 tclass,
593 SEPG_DB_TABLE__SETATTR |
594 SEPG_DB_TABLE__RELABELFROM,
595 audit_name,
596 true);
599 * check db_xxx:{relabelto} permission
601 sepgsql_avc_check_perms_label(seclabel,
602 tclass,
603 SEPG_DB_TABLE__RELABELTO,
604 audit_name,
605 true);
606 pfree(audit_name);
610 * sepgsql_relation_setattr
612 * It checks privileges to set attribute of the supplied relation
614 void
615 sepgsql_relation_setattr(Oid relOid)
617 Relation rel;
618 ScanKeyData skey;
619 SysScanDesc sscan;
620 HeapTuple oldtup;
621 HeapTuple newtup;
622 Form_pg_class oldform;
623 Form_pg_class newform;
624 ObjectAddress object;
625 char *audit_name;
626 uint16_t tclass;
628 switch (get_rel_relkind(relOid))
630 case RELKIND_RELATION:
631 case RELKIND_PARTITIONED_TABLE:
632 tclass = SEPG_CLASS_DB_TABLE;
633 break;
634 case RELKIND_SEQUENCE:
635 tclass = SEPG_CLASS_DB_SEQUENCE;
636 break;
637 case RELKIND_VIEW:
638 tclass = SEPG_CLASS_DB_VIEW;
639 break;
640 case RELKIND_INDEX:
641 /* deal with indexes specially */
642 sepgsql_index_modify(relOid);
643 return;
644 default:
645 /* other relkinds don't need additional work */
646 return;
650 * Fetch newer catalog
652 rel = table_open(RelationRelationId, AccessShareLock);
654 ScanKeyInit(&skey,
655 Anum_pg_class_oid,
656 BTEqualStrategyNumber, F_OIDEQ,
657 ObjectIdGetDatum(relOid));
659 sscan = systable_beginscan(rel, ClassOidIndexId, true,
660 SnapshotSelf, 1, &skey);
662 newtup = systable_getnext(sscan);
663 if (!HeapTupleIsValid(newtup))
664 elog(ERROR, "could not find tuple for relation %u", relOid);
665 newform = (Form_pg_class) GETSTRUCT(newtup);
668 * Fetch older catalog
670 oldtup = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
671 if (!HeapTupleIsValid(oldtup))
672 elog(ERROR, "cache lookup failed for relation %u", relOid);
673 oldform = (Form_pg_class) GETSTRUCT(oldtup);
676 * Does this ALTER command takes operation to namespace?
678 if (newform->relnamespace != oldform->relnamespace)
680 sepgsql_schema_remove_name(oldform->relnamespace);
681 sepgsql_schema_add_name(newform->relnamespace);
683 if (strcmp(NameStr(newform->relname), NameStr(oldform->relname)) != 0)
684 sepgsql_schema_rename(oldform->relnamespace);
687 * XXX - In the future version, db_tuple:{use} of system catalog entry
688 * shall be checked, if tablespace configuration is changed.
692 * check db_xxx:{setattr} permission
694 object.classId = RelationRelationId;
695 object.objectId = relOid;
696 object.objectSubId = 0;
697 audit_name = getObjectIdentity(&object, false);
699 sepgsql_avc_check_perms(&object,
700 tclass,
701 SEPG_DB_TABLE__SETATTR,
702 audit_name,
703 true);
704 pfree(audit_name);
706 ReleaseSysCache(oldtup);
707 systable_endscan(sscan);
708 table_close(rel, AccessShareLock);
712 * sepgsql_relation_setattr_extra
714 * It checks permission of the relation being referenced by extra attributes,
715 * such as pg_index entries. Like core PostgreSQL, sepgsql also does not deal
716 * with such entries as individual "objects", thus, modification of these
717 * entries shall be considered as setting an attribute of the underlying
718 * relation.
720 static void
721 sepgsql_relation_setattr_extra(Relation catalog,
722 Oid catindex_id,
723 Oid extra_oid,
724 AttrNumber anum_relation_id,
725 AttrNumber anum_extra_id)
727 ScanKeyData skey;
728 SysScanDesc sscan;
729 HeapTuple tuple;
730 Datum datum;
731 bool isnull;
733 ScanKeyInit(&skey, anum_extra_id,
734 BTEqualStrategyNumber, F_OIDEQ,
735 ObjectIdGetDatum(extra_oid));
737 sscan = systable_beginscan(catalog, catindex_id, true,
738 SnapshotSelf, 1, &skey);
739 tuple = systable_getnext(sscan);
740 if (!HeapTupleIsValid(tuple))
741 elog(ERROR, "could not find tuple for object %u in catalog \"%s\"",
742 extra_oid, RelationGetRelationName(catalog));
744 datum = heap_getattr(tuple, anum_relation_id,
745 RelationGetDescr(catalog), &isnull);
746 Assert(!isnull);
748 sepgsql_relation_setattr(DatumGetObjectId(datum));
750 systable_endscan(sscan);
754 * sepgsql_index_modify
755 * Handle index create, update, drop
757 * Unlike other relation kinds, indexes do not have their own security labels,
758 * so instead of doing checks directly, treat them as extra attributes of their
759 * owning tables; so check 'setattr' permissions on the table.
761 static void
762 sepgsql_index_modify(Oid indexOid)
764 Relation catalog = table_open(IndexRelationId, AccessShareLock);
766 /* check db_table:{setattr} permission of the table being indexed */
767 sepgsql_relation_setattr_extra(catalog,
768 IndexRelidIndexId,
769 indexOid,
770 Anum_pg_index_indrelid,
771 Anum_pg_index_indexrelid);
772 table_close(catalog, AccessShareLock);