contrib/sepgsql/database.c | 91 ++++++++++++++++++++++---- contrib/sepgsql/expected/create.out | 19 ++++++ contrib/sepgsql/hooks.c | 122 ++++++++++++++++++++++++---------- contrib/sepgsql/sepgsql.h | 3 +- contrib/sepgsql/sql/create.sql | 15 ++++ contrib/sepgsql/test_sepgsql | 2 +- doc/src/sgml/sepgsql.sgml | 30 ++++++++- 7 files changed, 231 insertions(+), 51 deletions(-) diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c index 7f15d9c..3faef63 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); + + 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(scontext, tcontext, + 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 0000000..8ff8c77 --- /dev/null +++ b/contrib/sepgsql/expected/create.out @@ -0,0 +1,19 @@ +-- +-- 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" +-- +-- clean-up +-- +DROP DATABASE IF EXISTS regtest_sepgsql_test_database; diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c index 331bbd7..fe71953 100644 --- a/contrib/sepgsql/hooks.c +++ b/contrib/sepgsql/hooks.c @@ -43,6 +43,22 @@ static fmgr_hook_type next_fmgr_hook = NULL; static ProcessUtility_hook_type next_ProcessUtility_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) */ static bool sepgsql_permissive; @@ -127,7 +143,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: @@ -308,44 +325,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: - - /* - * 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; + /* + * 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); + + 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); + + 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 +503,7 @@ _PG_init(void) /* ProcessUtility hook */ next_ProcessUtility_hook = ProcessUtility_hook; ProcessUtility_hook = sepgsql_utility_command; + + /* init contextual info */ + memset(&sepgsql_context_info, 0, sizeof(sepgsql_context_info)); } diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h index b4c1dfd..33b219f 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 0000000..6cd5656 --- /dev/null +++ b/contrib/sepgsql/sql/create.sql @@ -0,0 +1,15 @@ +-- +-- 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; + +-- +-- clean-up +-- +DROP DATABASE IF EXISTS regtest_sepgsql_test_database; diff --git a/contrib/sepgsql/test_sepgsql b/contrib/sepgsql/test_sepgsql index 9b7262a..52237e6 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 f2c9266..e45c258 100644 --- a/doc/src/sgml/sepgsql.sgml +++ b/doc/src/sgml/sepgsql.sgml @@ -421,6 +421,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 with its old security label, then relabelto with the supplied @@ -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.