contrib/sepgsql/expected/create.out | 30 +++++++ contrib/sepgsql/hooks.c | 45 ++++++++++ contrib/sepgsql/relation.c | 163 ++++++++++++++++++++++++++++++----- contrib/sepgsql/sql/create.sql | 12 +++ 4 files changed, 227 insertions(+), 23 deletions(-) diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out index 230e6dd..d2c30f0 100644 --- a/contrib/sepgsql/expected/create.out +++ b/contrib/sepgsql/expected/create.out @@ -16,8 +16,38 @@ LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_ CREATE SCHEMA regtest_schema; LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema" SET search_path = regtest_schema, public; +CREATE TABLE regtest_table (x serial primary key, y text); +NOTICE: CREATE TABLE will create implicit sequence "regtest_table_x_seq" for serial column "regtest_table.x" +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_table_x_seq" +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_table name="table regtest_table" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column tableoid" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmax" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmax" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column cmin" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column xmin" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column ctid" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column x" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column y" +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "regtest_table_pkey" for table "regtest_table" +ALTER TABLE regtest_table ADD COLUMN z int; +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_table_t:s0 tclass=db_column name="table regtest_table column z" +-- vacuum full internally create a new table and swap it later. +-- it does not require permission to create table/columns +VACUUM FULL regtest_table; +CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100; +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_view_t:s0 tclass=db_view name="view regtest_view" +CREATE SEQUENCE regtest_seq; +LOG: SELinux: allowed { add_name } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_schema_t:s0 tclass=db_schema name="schema regtest_schema" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_seq_t:s0 tclass=db_sequence name="sequence regtest_seq" -- -- clean-up -- DROP DATABASE IF EXISTS regtest_sepgsql_test_database; DROP SCHEMA IF EXISTS regtest_schema CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table regtest_table +drop cascades to view regtest_view +drop cascades to sequence regtest_seq diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c index bbc9a82..bff8918 100644 --- a/contrib/sepgsql/hooks.c +++ b/contrib/sepgsql/hooks.c @@ -42,6 +42,7 @@ static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL; static needs_fmgr_hook_type next_needs_fmgr_hook = NULL; static fmgr_hook_type next_fmgr_hook = NULL; static ProcessUtility_hook_type next_ProcessUtility_hook = NULL; +static ExecutorStart_hook_type next_ExecutorStart_hook = NULL; /* * GUC: sepgsql.permissive = (on|off) @@ -296,6 +297,46 @@ sepgsql_fmgr_hook(FmgrHookEventType event, } /* + * sepgsql_executor_start + * + * It saves contextual information during ExecutorStart to distinguish + * a case with/without permission checks later. + */ +static void +sepgsql_executor_start(QueryDesc *queryDesc, int eflags) +{ + sepgsql_context_info_t saved_context_info = sepgsql_context_info; + + PG_TRY(); + { + if (queryDesc->operation == CMD_SELECT) + sepgsql_context_info.cmdtype = T_SelectStmt; + else if (queryDesc->operation == CMD_INSERT) + sepgsql_context_info.cmdtype = T_InsertStmt; + else if (queryDesc->operation == CMD_DELETE) + sepgsql_context_info.cmdtype = T_DeleteStmt; + else if (queryDesc->operation == CMD_UPDATE) + sepgsql_context_info.cmdtype = T_UpdateStmt; + + /* + * XXX - If queryDesc->operation is not above four cases, an error + * shall be raised on the following executor stage soon. + */ + if (next_ExecutorStart_hook) + (*next_ExecutorStart_hook) (queryDesc, eflags); + else + standard_ExecutorStart(queryDesc, eflags); + } + PG_CATCH(); + { + sepgsql_context_info = saved_context_info; + PG_RE_THROW(); + } + PG_END_TRY(); + sepgsql_context_info = saved_context_info; +} + +/* * sepgsql_utility_command * * It tries to rough-grained control on utility commands; some of them can @@ -488,6 +529,10 @@ _PG_init(void) next_ProcessUtility_hook = ProcessUtility_hook; ProcessUtility_hook = sepgsql_utility_command; + /* ExecutorStart hook */ + next_ExecutorStart_hook = ExecutorStart_hook; + ExecutorStart_hook = sepgsql_executor_start; + /* init contextual info */ memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info)); } diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c index 0767382..a4ea22c 100644 --- a/contrib/sepgsql/relation.c +++ b/contrib/sepgsql/relation.c @@ -36,10 +36,16 @@ void sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum) { - char *scontext = sepgsql_get_client_label(); + Relation rel; + ScanKeyData skey[2]; + SysScanDesc sscan; + HeapTuple tuple; + char *scontext; char *tcontext; char *ncontext; + char audit_name[2*NAMEDATALEN + 20]; ObjectAddress object; + Form_pg_attribute attForm; /* * Only attributes within regular relation have individual security @@ -49,13 +55,44 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum) return; /* - * Compute a default security label when we create a new procedure object - * under the specified namespace. + * Compute a default security label of the new column underlying the + * specified relation, and check permission to create it. */ + rel = heap_open(AttributeRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relOid)); + ScanKeyInit(&skey[1], + Anum_pg_attribute_attnum, + BTEqualStrategyNumber, F_INT2EQ, + Int16GetDatum(attnum)); + + sscan = systable_beginscan(rel, AttributeRelidNumIndexId, true, + SnapshotSelf, 2, &skey[0]); + + tuple = systable_getnext(sscan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "catalog lookup failed for column %d of relation %u", + attnum, relOid); + + attForm = (Form_pg_attribute) GETSTRUCT(tuple); + scontext = sepgsql_get_client_label(); tcontext = sepgsql_get_label(RelationRelationId, relOid, 0); ncontext = sepgsql_compute_create(scontext, tcontext, SEPG_CLASS_DB_COLUMN); + /* + * check db_column:{create} permission + */ + snprintf(audit_name, sizeof(audit_name), "table %s column %s", + get_rel_name(relOid), NameStr(attForm->attname)); + sepgsql_avc_check_perms_label(ncontext, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__CREATE, + audit_name, + true); /* * Assign the default security label on a new procedure @@ -65,6 +102,9 @@ sepgsql_attribute_post_create(Oid relOid, AttrNumber attnum) object.objectSubId = attnum; SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); + systable_endscan(sscan); + heap_close(rel, AccessShareLock); + pfree(tcontext); pfree(ncontext); } @@ -127,10 +167,31 @@ sepgsql_relation_post_create(Oid relOid) Form_pg_class classForm; ObjectAddress object; uint16 tclass; + const char *tclass_text; char *scontext; /* subject */ char *tcontext; /* schema */ char *rcontext; /* relation */ char *ccontext; /* column */ + char audit_name[2*NAMEDATALEN + 20]; + + /* + * Some internally used code paths call heap_create_with_catalog(), then + * it launches this hook, even though it does not need permission check + * on creation of relation. So, we skip these cases. + */ + switch (sepgsql_context_info.cmdtype) + { + case T_CreateStmt: + case T_ViewStmt: + case T_CreateSeqStmt: + case T_CompositeTypeStmt: + case T_CreateForeignTableStmt: + case T_SelectStmt: + break; + default: + /* internal calls */ + return; + } /* * Fetch catalog record of the new relation. Because pg_class entry is not @@ -152,16 +213,36 @@ sepgsql_relation_post_create(Oid relOid) classForm = (Form_pg_class) GETSTRUCT(tuple); - if (classForm->relkind == RELKIND_RELATION) - tclass = SEPG_CLASS_DB_TABLE; - else if (classForm->relkind == RELKIND_SEQUENCE) - tclass = SEPG_CLASS_DB_SEQUENCE; - else if (classForm->relkind == RELKIND_VIEW) - tclass = SEPG_CLASS_DB_VIEW; - else - goto out; /* No need to assign individual labels */ + switch (classForm->relkind) + { + case RELKIND_RELATION: + tclass = SEPG_CLASS_DB_TABLE; + tclass_text = "table"; + break; + case RELKIND_SEQUENCE: + tclass = SEPG_CLASS_DB_SEQUENCE; + tclass_text = "sequence"; + break; + case RELKIND_VIEW: + tclass = SEPG_CLASS_DB_VIEW; + tclass_text = "view"; + break; + default: + goto out; + } /* + * check db_schema:{add_name} permission of the namespace + */ + object.classId = NamespaceRelationId; + object.objectId = classForm->relnamespace; + object.objectSubId = 0; + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__ADD_NAME, + getObjectDescription(&object), + true); + /* * Compute a default security label when we create a new relation object * under the specified namespace. */ @@ -171,6 +252,16 @@ sepgsql_relation_post_create(Oid relOid) rcontext = sepgsql_compute_create(scontext, tcontext, tclass); /* + * check db_xxx:{create} permission + */ + snprintf(audit_name, sizeof(audit_name), "%s %s", + tclass_text, NameStr(classForm->relname)); + sepgsql_avc_check_perms_label(rcontext, + tclass, + SEPG_DB_DATABASE__CREATE, + audit_name, + true); + /* * Assign the default security label on the new relation */ object.classId = RelationRelationId; @@ -184,26 +275,52 @@ sepgsql_relation_post_create(Oid relOid) */ if (classForm->relkind == RELKIND_RELATION) { - AttrNumber index; + Relation arel; + ScanKeyData akey; + SysScanDesc ascan; + HeapTuple atup; + Form_pg_attribute attForm; + + arel = heap_open(AttributeRelationId, AccessShareLock); + + ScanKeyInit(&akey, + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relOid)); - ccontext = sepgsql_compute_create(scontext, rcontext, - SEPG_CLASS_DB_COLUMN); - for (index = FirstLowInvalidHeapAttributeNumber + 1; - index <= classForm->relnatts; - index++) + ascan = systable_beginscan(arel, AttributeRelidNumIndexId, true, + SnapshotSelf, 1, &akey); + + while (HeapTupleIsValid(atup = systable_getnext(ascan))) { - if (index == InvalidAttrNumber) - continue; + attForm = (Form_pg_attribute) GETSTRUCT(atup); + + snprintf(audit_name, sizeof(audit_name), "%s %s column %s", + tclass_text, + NameStr(classForm->relname), + NameStr(attForm->attname)); - if (index == ObjectIdAttributeNumber && !classForm->relhasoids) - continue; + ccontext = sepgsql_compute_create(scontext, + rcontext, + SEPG_CLASS_DB_COLUMN); + /* + * check db_column:{create} permission + */ + sepgsql_avc_check_perms_label(ccontext, + SEPG_CLASS_DB_COLUMN, + SEPG_DB_COLUMN__CREATE, + audit_name, + true); object.classId = RelationRelationId; object.objectId = relOid; - object.objectSubId = index; + object.objectSubId = attForm->attnum; SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ccontext); + + pfree(ccontext); } - pfree(ccontext); + systable_endscan(ascan); + heap_close(arel, AccessShareLock); } pfree(rcontext); out: diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql index a03c977..9cb2b60 100644 --- a/contrib/sepgsql/sql/create.sql +++ b/contrib/sepgsql/sql/create.sql @@ -13,6 +13,18 @@ CREATE SCHEMA regtest_schema; SET search_path = regtest_schema, public; +CREATE TABLE regtest_table (x serial primary key, y text); + +ALTER TABLE regtest_table ADD COLUMN z int; + +-- vacuum full internally create a new table and swap it later. +-- it does not require permission to create table/columns +VACUUM FULL regtest_table; + +CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100; + +CREATE SEQUENCE regtest_seq; + -- -- clean-up --