contrib/sepgsql/database.c | 93 +++++++++++++++++++++++++++---- contrib/sepgsql/expected/create.out | 19 ++++++ contrib/sepgsql/hooks.c | 104 +++++++++++++++++++++++------------ contrib/sepgsql/sepgsql.h | 10 +++ contrib/sepgsql/sql/create.sql | 15 +++++ contrib/sepgsql/test_sepgsql | 2 +- 6 files changed, 196 insertions(+), 47 deletions(-) diff --git a/contrib/sepgsql/database.c b/contrib/sepgsql/database.c index 7f15d9c..65941c4 100644 --- a/contrib/sepgsql/database.c +++ b/contrib/sepgsql/database.c @@ -10,33 +10,104 @@ */ #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) { - 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 *dtemplate; + char audit_name[NAMEDATALEN + 20]; + ObjectAddress object; + Form_pg_database datForm; + + Assert(sepgsql_context_info.cmdtype == T_CreatedbStmt); + + /* + * Because OID of source database is not saved within pg_database + * catalog entry, we pick up it from the contextual information + * saved on entrypoint of ProcessUtility + */ + dtemplate = sepgsql_context_info.createdb_template; + if (!dtemplate) + dtemplate = "template0"; + + 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); - ncontext = sepgsql_compute_create(scontext, tcontext, + 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 0000000..cc60118 --- /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 template0" +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..bbc9a82 100644 --- a/contrib/sepgsql/hooks.c +++ b/contrib/sepgsql/hooks.c @@ -31,6 +31,7 @@ PG_MODULE_MAGIC; * Declarations */ void _PG_init(void); +sepgsql_context_info_t sepgsql_context_info; /* * Saved hook entries (if stacked) @@ -308,44 +309,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_template + = 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 +487,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..f192fd4 100644 --- a/contrib/sepgsql/sepgsql.h +++ b/contrib/sepgsql/sepgsql.h @@ -213,6 +213,16 @@ /* * hooks.c */ +typedef struct +{ + NodeTag cmdtype; + + /* User given template database on CREATE DATABASE, elsewhere NULL */ + char *createdb_template; +} sepgsql_context_info_t; + +extern sepgsql_context_info_t sepgsql_context_info; + extern bool sepgsql_get_permissive(void); extern bool sepgsql_get_debug_audit(void); 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"