1 /* -------------------------------------------------------------------------
3 * contrib/sepgsql/relation.c
5 * Routines corresponding to relation/attribute objects
7 * Copyright (c) 2010-2022, PostgreSQL Global Development Group
9 * -------------------------------------------------------------------------
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"
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.
43 sepgsql_attribute_post_create(Oid relOid
, AttrNumber attnum
)
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
)
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
);
71 Anum_pg_attribute_attrelid
,
72 BTEqualStrategyNumber
, F_OIDEQ
,
73 ObjectIdGetDatum(relOid
));
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",
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
,
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
,
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
);
128 * sepgsql_attribute_drop
130 * It checks privileges to drop the supplied column.
133 sepgsql_attribute_drop(Oid relOid
, AttrNumber attnum
)
135 ObjectAddress object
;
137 char relkind
= get_rel_relkind(relOid
);
139 if (relkind
!= RELKIND_RELATION
&& relkind
!= RELKIND_PARTITIONED_TABLE
)
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
,
159 * sepgsql_attribute_relabel
161 * It checks privileges to relabel the supplied column
165 sepgsql_attribute_relabel(Oid relOid
, AttrNumber attnum
,
166 const char *seclabel
)
168 ObjectAddress object
;
170 char relkind
= get_rel_relkind(relOid
);
172 if (relkind
!= RELKIND_RELATION
&& relkind
!= RELKIND_PARTITIONED_TABLE
)
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
,
193 * check db_column:{relabelto} permission
195 sepgsql_avc_check_perms_label(seclabel
,
196 SEPG_CLASS_DB_COLUMN
,
197 SEPG_DB_PROCEDURE__RELABELTO
,
204 * sepgsql_attribute_setattr
206 * It checks privileges to alter the supplied column.
209 sepgsql_attribute_setattr(Oid relOid
, AttrNumber attnum
)
211 ObjectAddress object
;
213 char relkind
= get_rel_relkind(relOid
);
215 if (relkind
!= RELKIND_RELATION
&& relkind
!= RELKIND_PARTITIONED_TABLE
)
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
,
235 * sepgsql_relation_post_create
237 * The post creation hook of relation/attribute
240 sepgsql_relation_post_create(Oid relOid
)
246 Form_pg_class classForm
;
247 ObjectAddress object
;
249 char *scontext
; /* subject */
250 char *tcontext
; /* schema */
251 char *rcontext
; /* relation */
252 char *ccontext
; /* column */
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
);
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
)
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),
293 switch (classForm
->relkind
)
295 case RELKIND_RELATION
:
296 case RELKIND_PARTITIONED_TABLE
:
297 tclass
= SEPG_CLASS_DB_TABLE
;
299 case RELKIND_SEQUENCE
:
300 tclass
= SEPG_CLASS_DB_SEQUENCE
;
303 tclass
= SEPG_CLASS_DB_VIEW
;
306 /* deal with indexes specially; no need for tclass */
307 sepgsql_index_modify(relOid
);
310 /* ignore other relkinds */
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
,
334 SEPG_DB_DATABASE__CREATE
,
339 * Assign the default security label on the new regular or partitioned
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
)
357 Form_pg_attribute attForm
;
359 arel
= table_open(AttributeRelationId
, AccessShareLock
);
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
,
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
,
393 object
.classId
= RelationRelationId
;
394 object
.objectId
= relOid
;
395 object
.objectSubId
= attForm
->attnum
;
396 SetSecurityLabel(&object
, SEPGSQL_LABEL_TAG
, ccontext
);
400 systable_endscan(ascan
);
401 table_close(arel
, AccessShareLock
);
406 systable_endscan(sscan
);
407 table_close(rel
, AccessShareLock
);
411 * sepgsql_relation_drop
413 * It checks privileges to drop the supplied relation.
416 sepgsql_relation_drop(Oid relOid
)
418 ObjectAddress object
;
421 char relkind
= get_rel_relkind(relOid
);
425 case RELKIND_RELATION
:
426 case RELKIND_PARTITIONED_TABLE
:
427 tclass
= SEPG_CLASS_DB_TABLE
;
429 case RELKIND_SEQUENCE
:
430 tclass
= SEPG_CLASS_DB_SEQUENCE
;
433 tclass
= SEPG_CLASS_DB_VIEW
;
436 /* ignore indexes on toast tables */
437 if (get_rel_namespace(relOid
) == PG_TOAST_NAMESPACE
)
439 /* other indexes are handled specially below; no need for tclass */
442 /* ignore other relkinds */
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
,
461 /* deal with indexes specially */
462 if (relkind
== RELKIND_INDEX
)
464 sepgsql_index_modify(relOid
);
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
,
484 * check db_column:{drop} permission
486 if (relkind
== RELKIND_RELATION
|| relkind
== RELKIND_PARTITIONED_TABLE
)
488 Form_pg_attribute attForm
;
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
)
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
,
514 ReleaseCatCacheList(attrList
);
519 * sepgsql_relation_truncate
521 * Check privileges to TRUNCATE the supplied relation.
524 sepgsql_relation_truncate(Oid relOid
)
526 ObjectAddress object
;
529 char relkind
= get_rel_relkind(relOid
);
533 case RELKIND_RELATION
:
534 case RELKIND_PARTITIONED_TABLE
:
535 tclass
= SEPG_CLASS_DB_TABLE
;
538 /* ignore other relkinds */
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
,
552 SEPG_DB_TABLE__TRUNCATE
,
559 * sepgsql_relation_relabel
561 * It checks privileges to relabel the supplied relation by the `seclabel'.
564 sepgsql_relation_relabel(Oid relOid
, const char *seclabel
)
566 ObjectAddress object
;
568 char relkind
= get_rel_relkind(relOid
);
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
;
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
,
593 SEPG_DB_TABLE__SETATTR
|
594 SEPG_DB_TABLE__RELABELFROM
,
599 * check db_xxx:{relabelto} permission
601 sepgsql_avc_check_perms_label(seclabel
,
603 SEPG_DB_TABLE__RELABELTO
,
610 * sepgsql_relation_setattr
612 * It checks privileges to set attribute of the supplied relation
615 sepgsql_relation_setattr(Oid relOid
)
622 Form_pg_class oldform
;
623 Form_pg_class newform
;
624 ObjectAddress object
;
628 switch (get_rel_relkind(relOid
))
630 case RELKIND_RELATION
:
631 case RELKIND_PARTITIONED_TABLE
:
632 tclass
= SEPG_CLASS_DB_TABLE
;
634 case RELKIND_SEQUENCE
:
635 tclass
= SEPG_CLASS_DB_SEQUENCE
;
638 tclass
= SEPG_CLASS_DB_VIEW
;
641 /* deal with indexes specially */
642 sepgsql_index_modify(relOid
);
645 /* other relkinds don't need additional work */
650 * Fetch newer catalog
652 rel
= table_open(RelationRelationId
, AccessShareLock
);
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
,
701 SEPG_DB_TABLE__SETATTR
,
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
721 sepgsql_relation_setattr_extra(Relation catalog
,
724 AttrNumber anum_relation_id
,
725 AttrNumber anum_extra_id
)
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
);
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.
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
,
770 Anum_pg_index_indrelid
,
771 Anum_pg_index_indexrelid
);
772 table_close(catalog
, AccessShareLock
);