From e1042a348421bc16f4d4307228a9951e38a984f1 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Wed, 21 Dec 2011 09:12:43 -0500 Subject: [PATCH] sepgsql: Check CREATE permissions for some object types. KaiGai Kohei, reviewed by Dimitri Fontaine and me. --- contrib/sepgsql/database.c | 91 ++++++++++++-- contrib/sepgsql/expected/create.out | 80 ++++++++++++ contrib/sepgsql/hooks.c | 188 ++++++++++++++++++++++------ contrib/sepgsql/proc.c | 55 +++++++- contrib/sepgsql/relation.c | 144 +++++++++++++++++---- contrib/sepgsql/schema.c | 50 +++++++- contrib/sepgsql/sepgsql.h | 3 +- contrib/sepgsql/sql/create.sql | 46 +++++++ contrib/sepgsql/test_sepgsql | 2 +- doc/src/sgml/sepgsql.sgml | 30 ++++- 10 files changed, 606 insertions(+), 83 deletions(-) create mode 100644 contrib/sepgsql/expected/create.out create mode 100644 contrib/sepgsql/sql/create.sql diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c index 7f15d9ce71..3faef63a16 100644 --- a/contrib/sepgsql/database.c +++ b/contrib/sepgsql/database.c @@ -10,33 +10,100 @@ */ #include "postgres.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/sysattr.h" #include "catalog/dependency.h" #include "catalog/pg_database.h" +#include "catalog/indexing.h" +#include "commands/dbcommands.h" #include "commands/seclabel.h" +#include "utils/fmgroids.h" +#include "utils/tqual.h" #include "sepgsql.h" +/* + * sepgsql_database_post_create + * + * This routine assigns a default security label on a newly defined + * database, and check permission needed for its creation. + */ void -sepgsql_database_post_create(Oid databaseId) +sepgsql_database_post_create(Oid databaseId, const char *dtemplate) { - char *scontext = sepgsql_get_client_label(); - char *tcontext; - char *ncontext; - ObjectAddress object; + Relation rel; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; + char *tcontext; + char *ncontext; + char audit_name[NAMEDATALEN + 20]; + ObjectAddress object; + Form_pg_database datForm; + + /* + * Oid of the source database is not saved in pg_database catalog, + * so we collect its identifier using contextual information. + * If NULL, its default is "template1" according to createdb(). + */ + if (!dtemplate) + dtemplate = "template1"; + + object.classId = DatabaseRelationId; + object.objectId = get_database_oid(dtemplate, false); + object.objectSubId = 0; + + tcontext = sepgsql_get_label(object.classId, + object.objectId, + object.objectSubId); + /* + * check db_database:{getattr} permission + */ + snprintf(audit_name, sizeof(audit_name), "database %s", dtemplate); + sepgsql_avc_check_perms_label(tcontext, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__GETATTR, + audit_name, + true); /* * Compute a default security label of the newly created database * based on a pair of security label of client and source database. * - * XXX - Right now, this logic uses "template1" as its source, because - * here is no way to know the Oid of source database. + * XXX - uncoming version of libselinux supports to take object + * name to handle special treatment on default security label. */ - object.classId = DatabaseRelationId; - object.objectId = TemplateDbOid; - object.objectSubId = 0; - tcontext = GetSecurityLabel(&object, SEPGSQL_LABEL_TAG); + rel = heap_open(DatabaseRelationId, AccessShareLock); - ncontext = sepgsql_compute_create(scontext, tcontext, + ScanKeyInit(&skey, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(databaseId)); + + sscan = systable_beginscan(rel, DatabaseOidIndexId, true, + SnapshotSelf, 1, &skey); + tuple = systable_getnext(sscan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "catalog lookup failed for database %u", databaseId); + + datForm = (Form_pg_database) GETSTRUCT(tuple); + + ncontext = sepgsql_compute_create(sepgsql_get_client_label(), + tcontext, SEPG_CLASS_DB_DATABASE); + /* + * check db_database:{create} permission + */ + snprintf(audit_name, sizeof(audit_name), + "database %s", NameStr(datForm->datname)); + sepgsql_avc_check_perms_label(ncontext, + SEPG_CLASS_DB_DATABASE, + SEPG_DB_DATABASE__CREATE, + audit_name, + true); + + systable_endscan(sscan); + heap_close(rel, AccessShareLock); /* * Assign the default security label on the new database diff --git a/contrib/sepgsql/expected/create.out b/contrib/sepgsql/expected/create.out new file mode 100644 index 0000000000..0f04a3e739 --- /dev/null +++ b/contrib/sepgsql/expected/create.out @@ -0,0 +1,80 @@ +-- +-- Regression Test for Creation of Object Permission Checks +-- +-- confirm required permissions using audit messages +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0 +(1 row) + +SET sepgsql.debug_audit = true; +SET client_min_messages = LOG; +CREATE DATABASE regtest_sepgsql_test_database; +LOG: SELinux: allowed { getattr } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=system_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database template1" +LOG: SELinux: allowed { create } scontext=unconfined_u:unconfined_r:unconfined_t:s0 tcontext=unconfined_u:object_r:sepgsql_db_t:s0 tclass=db_database name="database regtest_sepgsql_test_database" +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" +CREATE TABLE regtest_table_2 (a int) WITH OIDS; +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_2" +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_2 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_2 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_2 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_2 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_2 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_2 column oid" +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_2 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_2 column a" +-- corresponding toast table should not have label and permission checks +ALTER TABLE regtest_table_2 ADD COLUMN b text; +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_2 column b" +-- VACUUM FULL internally create a new table and swap them later. +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" +CREATE TYPE regtest_comptype AS (a int, b text); +CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql + AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END'; +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_proc_exec_t:s0 tclass=db_procedure name="function regtest_func(text,integer[])" +CREATE AGGREGATE regtest_agg ( + sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond1 = '0' +); +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_proc_exec_t:s0 tclass=db_procedure name="function regtest_agg(integer)" +-- +-- clean-up +-- +DROP DATABASE IF EXISTS regtest_sepgsql_test_database; +DROP SCHEMA IF EXISTS regtest_schema CASCADE; +NOTICE: drop cascades to 7 other objects +DETAIL: drop cascades to table regtest_table +drop cascades to table regtest_table_2 +drop cascades to view regtest_view +drop cascades to sequence regtest_seq +drop cascades to type regtest_comptype +drop cascades to function regtest_func(text,integer[]) +drop cascades to function regtest_agg(integer) diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c index 331bbd7e78..823297108f 100644 --- a/contrib/sepgsql/hooks.c +++ b/contrib/sepgsql/hooks.c @@ -41,6 +41,23 @@ 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; + +/* + * Contextual information on DDL commands + */ +typedef struct +{ + NodeTag cmdtype; + + /* + * Name of the template database given by users on CREATE DATABASE + * command. Elsewhere (including the case of default) NULL. + */ + const char *createdb_dtemplate; +} sepgsql_context_info_t; + +static sepgsql_context_info_t sepgsql_context_info; /* * GUC: sepgsql.permissive = (on|off) @@ -127,7 +144,8 @@ sepgsql_object_access(ObjectAccessType access, switch (classId) { case DatabaseRelationId: - sepgsql_database_post_create(objectId); + sepgsql_database_post_create(objectId, + sepgsql_context_info.createdb_dtemplate); break; case NamespaceRelationId: @@ -136,7 +154,30 @@ sepgsql_object_access(ObjectAccessType access, case RelationRelationId: if (subId == 0) - sepgsql_relation_post_create(objectId); + { + /* + * All cases we want to apply permission checks on + * creation of a new relation are invocation of the + * heap_create_with_catalog via DefineRelation or + * OpenIntoRel. + * Elsewhere, we need neither assignment of security + * label nor permission checks. + */ + switch (sepgsql_context_info.cmdtype) + { + case T_CreateStmt: + case T_ViewStmt: + case T_CreateSeqStmt: + case T_CompositeTypeStmt: + case T_CreateForeignTableStmt: + case T_SelectStmt: + sepgsql_relation_post_create(objectId); + break; + default: + /* via make_new_heap() */ + break; + } + } else sepgsql_attribute_post_create(objectId, subId); break; @@ -294,6 +335,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 * @@ -308,44 +389,74 @@ sepgsql_utility_command(Node *parsetree, DestReceiver *dest, char *completionTag) { - if (next_ProcessUtility_hook) - (*next_ProcessUtility_hook) (parsetree, queryString, params, - isTopLevel, dest, completionTag); + sepgsql_context_info_t saved_context_info = sepgsql_context_info; + ListCell *cell; - /* - * Check command tag to avoid nefarious operations - */ - switch (nodeTag(parsetree)) + PG_TRY(); { - case T_LoadStmt: + /* + * Check command tag to avoid nefarious operations, and save the + * current contextual information to determine whether we should + * apply permission checks here, or not. + */ + sepgsql_context_info.cmdtype = nodeTag(parsetree); - /* - * We reject LOAD command across the board on enforcing mode, - * because a binary module can arbitrarily override hooks. - */ - if (sepgsql_getenforce()) - { - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("SELinux: LOAD is not permitted"))); - } - break; - default: + switch (nodeTag(parsetree)) + { + case T_CreatedbStmt: + /* + * We hope to reference name of the source database, but it + * does not appear in system catalog. So, we save it here. + */ + foreach (cell, ((CreatedbStmt *) parsetree)->options) + { + DefElem *defel = (DefElem *) lfirst(cell); - /* - * Right now we don't check any other utility commands, because it - * needs more detailed information to make access control decision - * here, but we don't want to have two parse and analyze routines - * individually. - */ - break; + if (strcmp(defel->defname, "template") == 0) + { + sepgsql_context_info.createdb_dtemplate + = strVal(defel->arg); + break; + } + } + break; + + case T_LoadStmt: + /* + * We reject LOAD command across the board on enforcing mode, + * because a binary module can arbitrarily override hooks. + */ + if (sepgsql_getenforce()) + { + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("SELinux: LOAD is not permitted"))); + } + break; + default: + /* + * Right now we don't check any other utility commands, + * because it needs more detailed information to make access + * control decision here, but we don't want to have two parse + * and analyze routines individually. + */ + break; + } + + if (next_ProcessUtility_hook) + (*next_ProcessUtility_hook) (parsetree, queryString, params, + isTopLevel, dest, completionTag); + else + standard_ProcessUtility(parsetree, queryString, params, + isTopLevel, dest, completionTag); } - - /* - * Original implementation - */ - standard_ProcessUtility(parsetree, queryString, params, - isTopLevel, dest, completionTag); + PG_CATCH(); + { + sepgsql_context_info = saved_context_info; + PG_RE_THROW(); + } + PG_END_TRY(); + sepgsql_context_info = saved_context_info; } /* @@ -456,4 +567,11 @@ _PG_init(void) /* ProcessUtility hook */ 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/proc.c b/contrib/sepgsql/proc.c index 9630d45689..14231c4aa8 100644 --- a/contrib/sepgsql/proc.c +++ b/contrib/sepgsql/proc.c @@ -18,6 +18,7 @@ #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "commands/seclabel.h" +#include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/tqual.h" @@ -37,11 +38,13 @@ sepgsql_proc_post_create(Oid functionId) ScanKeyData skey; SysScanDesc sscan; HeapTuple tuple; - Oid namespaceId; - ObjectAddress object; char *scontext; char *tcontext; char *ncontext; + int i; + StringInfoData audit_name; + ObjectAddress object; + Form_pg_proc proForm; /* * Fetch namespace of the new procedure. Because pg_proc entry is not @@ -61,20 +64,53 @@ sepgsql_proc_post_create(Oid functionId) if (!HeapTupleIsValid(tuple)) elog(ERROR, "catalog lookup failed for proc %u", functionId); - namespaceId = ((Form_pg_proc) GETSTRUCT(tuple))->pronamespace; + proForm = (Form_pg_proc) GETSTRUCT(tuple); + + /* + * check db_schema:{add_name} permission of the namespace + */ + object.classId = NamespaceRelationId; + object.objectId = proForm->pronamespace; + object.objectSubId = 0; + sepgsql_avc_check_perms(&object, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__ADD_NAME, + getObjectDescription(&object), + true); + /* + * XXX - db_language:{implement} also should be checked here + */ - systable_endscan(sscan); - heap_close(rel, AccessShareLock); /* * Compute a default security label when we create a new procedure object * under the specified namespace. */ scontext = sepgsql_get_client_label(); - tcontext = sepgsql_get_label(NamespaceRelationId, namespaceId, 0); + tcontext = sepgsql_get_label(NamespaceRelationId, + proForm->pronamespace, 0); ncontext = sepgsql_compute_create(scontext, tcontext, SEPG_CLASS_DB_PROCEDURE); + /* + * check db_procedure:{create} permission + */ + initStringInfo(&audit_name); + appendStringInfo(&audit_name, "function %s(", NameStr(proForm->proname)); + for (i=0; i < proForm->pronargs; i++) + { + Oid typeoid = proForm->proargtypes.values[i]; + if (i > 0) + appendStringInfoChar(&audit_name, ','); + appendStringInfoString(&audit_name, format_type_be(typeoid)); + } + appendStringInfoChar(&audit_name, ')'); + + sepgsql_avc_check_perms_label(ncontext, + SEPG_CLASS_DB_PROCEDURE, + SEPG_DB_PROCEDURE__CREATE, + audit_name.data, + true); /* * Assign the default security label on a new procedure */ @@ -83,6 +119,13 @@ sepgsql_proc_post_create(Oid functionId) object.objectSubId = 0; SetSecurityLabel(&object, SEPGSQL_LABEL_TAG, ncontext); + /* + * Cleanup + */ + systable_endscan(sscan); + heap_close(rel, AccessShareLock); + + pfree(audit_name.data); pfree(tcontext); pfree(ncontext); } diff --git a/contrib/sepgsql/relation.c b/contrib/sepgsql/relation.c index 07673825e5..b4abc8eac0 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,12 @@ 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]; /* * Fetch catalog record of the new relation. Because pg_class entry is not @@ -152,15 +194,35 @@ 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. @@ -170,6 +232,16 @@ sepgsql_relation_post_create(Oid relOid) classForm->relnamespace, 0); 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 */ @@ -184,26 +256,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; - ccontext = sepgsql_compute_create(scontext, rcontext, - SEPG_CLASS_DB_COLUMN); - for (index = FirstLowInvalidHeapAttributeNumber + 1; - index <= classForm->relnatts; - index++) + arel = heap_open(AttributeRelationId, AccessShareLock); + + ScanKeyInit(&akey, + Anum_pg_attribute_attrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relOid)); + + 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); - if (index == ObjectIdAttributeNumber && !classForm->relhasoids) - continue; + snprintf(audit_name, sizeof(audit_name), "%s %s column %s", + tclass_text, + NameStr(classForm->relname), + NameStr(attForm->attname)); + + 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/schema.c b/contrib/sepgsql/schema.c index a167be17b2..c8bb8c9275 100644 --- a/contrib/sepgsql/schema.c +++ b/contrib/sepgsql/schema.c @@ -10,12 +10,18 @@ */ #include "postgres.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/sysattr.h" #include "catalog/dependency.h" +#include "catalog/indexing.h" #include "catalog/pg_database.h" #include "catalog/pg_namespace.h" #include "commands/seclabel.h" #include "miscadmin.h" +#include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/tqual.h" #include "sepgsql.h" @@ -28,19 +34,55 @@ void sepgsql_schema_post_create(Oid namespaceId) { - char *scontext; + Relation rel; + ScanKeyData skey; + SysScanDesc sscan; + HeapTuple tuple; char *tcontext; char *ncontext; - ObjectAddress object; + char audit_name[NAMEDATALEN + 20]; + ObjectAddress object; + Form_pg_namespace nspForm; /* * Compute a default security label when we create a new schema object * under the working database. + * + * XXX - uncoming version of libselinux supports to take object + * name to handle special treatment on default security label; + * such as special label on "pg_temp" schema. */ - scontext = sepgsql_get_client_label(); + rel = heap_open(NamespaceRelationId, AccessShareLock); + + ScanKeyInit(&skey, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceId)); + + sscan = systable_beginscan(rel, NamespaceOidIndexId, true, + SnapshotSelf, 1, &skey); + tuple = systable_getnext(sscan); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "catalog lookup failed for namespace %u", namespaceId); + + nspForm = (Form_pg_namespace) GETSTRUCT(tuple); + tcontext = sepgsql_get_label(DatabaseRelationId, MyDatabaseId, 0); - ncontext = sepgsql_compute_create(scontext, tcontext, + ncontext = sepgsql_compute_create(sepgsql_get_client_label(), + tcontext, SEPG_CLASS_DB_SCHEMA); + /* + * check db_schema:{create} + */ + snprintf(audit_name, sizeof(audit_name), + "schema %s", NameStr(nspForm->nspname)); + sepgsql_avc_check_perms_label(ncontext, + SEPG_CLASS_DB_SCHEMA, + SEPG_DB_SCHEMA__CREATE, + audit_name, + true); + systable_endscan(sscan); + heap_close(rel, AccessShareLock); /* * Assign the default security label on a new procedure diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h index b4c1dfdfe7..33b219fffb 100644 --- a/contrib/sepgsql/sepgsql.h +++ b/contrib/sepgsql/sepgsql.h @@ -286,7 +286,8 @@ extern bool sepgsql_dml_privileges(List *rangeTabls, bool abort); /* * database.c */ -extern void sepgsql_database_post_create(Oid databaseId); +extern void sepgsql_database_post_create(Oid databaseId, + const char *dtemplate); extern void sepgsql_database_relabel(Oid databaseId, const char *seclabel); /* diff --git a/contrib/sepgsql/sql/create.sql b/contrib/sepgsql/sql/create.sql new file mode 100644 index 0000000000..b0695b41a9 --- /dev/null +++ b/contrib/sepgsql/sql/create.sql @@ -0,0 +1,46 @@ +-- +-- Regression Test for Creation of Object Permission Checks +-- + +-- confirm required permissions using audit messages +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0 +SET sepgsql.debug_audit = true; +SET client_min_messages = LOG; + +CREATE DATABASE regtest_sepgsql_test_database; + +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; + +CREATE TABLE regtest_table_2 (a int) WITH OIDS; + +-- corresponding toast table should not have label and permission checks +ALTER TABLE regtest_table_2 ADD COLUMN b text; + +-- VACUUM FULL internally create a new table and swap them later. +VACUUM FULL regtest_table; + +CREATE VIEW regtest_view AS SELECT * FROM regtest_table WHERE x < 100; + +CREATE SEQUENCE regtest_seq; + +CREATE TYPE regtest_comptype AS (a int, b text); + +CREATE FUNCTION regtest_func(text,int[]) RETURNS bool LANGUAGE plpgsql + AS 'BEGIN RAISE NOTICE ''regtest_func => %'', $1; RETURN true; END'; + +CREATE AGGREGATE regtest_agg ( + sfunc1 = int4pl, basetype = int4, stype1 = int4, initcond1 = '0' +); + +-- +-- clean-up +-- +DROP DATABASE IF EXISTS regtest_sepgsql_test_database; + +DROP SCHEMA IF EXISTS regtest_schema CASCADE; diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql index 9b7262ae82..52237e6691 100755 --- a/contrib/sepgsql/test_sepgsql +++ b/contrib/sepgsql/test_sepgsql @@ -259,6 +259,6 @@ echo "found ${NUM}" echo echo "============== running sepgsql regression tests ==============" -make REGRESS="label dml misc" REGRESS_OPTS="--launcher ./launcher" installcheck +make REGRESS="label dml create misc" REGRESS_OPTS="--launcher ./launcher" installcheck # exit with the exit code provided by "make" diff --git a/doc/src/sgml/sepgsql.sgml b/doc/src/sgml/sepgsql.sgml index f2c9266709..e45c258ac8 100644 --- a/doc/src/sgml/sepgsql.sgml +++ b/doc/src/sgml/sepgsql.sgml @@ -420,6 +420,33 @@ UPDATE t1 SET x = 2, y = md5sum(y) WHERE z = 100; DDL Permissions + + SELinux defines several permissions to control common + operations for each object types; such as creation, alter, drop and + relabel of security label. In addition, several object types has its + special permissions to control its characteristic operations; such as + addition or deletion of name entries underlying a particular schema. + + + When CREATE command is executed, create will + be checked on the object being constructed for each object types. + A default security label shall be assigned on the new database object, + and the create permission needs to be allowed on the pair + of security label of the client and the new object itself. + We consider construct a table and + underlying columns at the same time, so it requires users permission + to create both of table and columns. + + + A few additional checks are applied depending on object types. + On , getattr permission + shall be checked on the source or template database of the new database, + not only create on the new database. + On creation of objects underlying a particula schema (tables, views, + sequences and procedures), add_name shall be also chechked + on the schema, not only create on the new object itself. + + When is executed, setattr and relabelfrom will be checked on the object being relabeled @@ -509,7 +536,8 @@ postgres=# SELECT cid, cname, show_credit(cid) FROM customer; Data Definition Language (DDL) Permissions - Due to implementation restrictions, DDL permissions are not checked. + Due to implementation restrictions, some of DDL permissions are not + checked.