From 523176cbf14a3414170a83dd43686c0eccdc61c6 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Thu, 15 Mar 2012 16:08:40 -0400 Subject: [PATCH] sepgsql_setcon(). This is intended as infrastructure to allow sepgsql to cooperate with connection pooling software, by allowing the effective security label to be set for each new connection. KaiGai Kohei, reviewed by Yeb Havinga. --- contrib/sepgsql/expected/label.out | 346 +++++++++++++++++++++++++++++ contrib/sepgsql/label.c | 216 +++++++++++++++++- contrib/sepgsql/selinux.c | 6 + contrib/sepgsql/sepgsql-regtest.te | 113 +++++++++- contrib/sepgsql/sepgsql.h | 3 + contrib/sepgsql/sepgsql.sql.in | 1 + contrib/sepgsql/sql/label.sql | 155 +++++++++++++ doc/src/sgml/sepgsql.sgml | 114 +++++++++- 8 files changed, 931 insertions(+), 23 deletions(-) diff --git a/contrib/sepgsql/expected/label.out b/contrib/sepgsql/expected/label.out index bac169f37b..f9587dee57 100644 --- a/contrib/sepgsql/expected/label.out +++ b/contrib/sepgsql/expected/label.out @@ -26,7 +26,33 @@ CREATE FUNCTION f4 () RETURNS text AS 'SELECT sepgsql_getcon()' LANGUAGE sql; SECURITY LABEL ON FUNCTION f4() + IS 'system_u:object_r:sepgsql_nosuch_trusted_proc_exec_t:s0'; +CREATE FUNCTION f5 (text) RETURNS bool + AS 'SELECT sepgsql_setcon($1)' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION f5(text) IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0'; +CREATE TABLE auth_tbl(uname text, credential text, label text); +INSERT INTO auth_tbl + VALUES ('foo', 'acbd18db4cc2f85cedef654fccc4a4d8', 'sepgsql_regtest_foo_t:s0'), + ('var', 'b2145aac704ce76dbe1ac7adac535b23', 'sepgsql_regtest_var_t:s0'), + ('baz', 'b2145aac704ce76dbe1ac7adac535b23', 'sepgsql_regtest_baz_t:s0'); +SECURITY LABEL ON TABLE auth_tbl + IS 'system_u:object_r:sepgsql_secret_table_t:s0'; +CREATE FUNCTION auth_func(text, text) RETURNS bool + LANGUAGE sql + AS 'SELECT sepgsql_setcon(regexp_replace(sepgsql_getcon(), ''_r:.*$'', ''_r:'' || label)) + FROM auth_tbl WHERE uname = $1 AND credential = $2'; +SECURITY LABEL ON FUNCTION auth_func(text,text) + IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0'; +CREATE TABLE foo_tbl(a int, b text); +INSERT INTO foo_tbl VALUES (1, 'aaa'), (2,'bbb'), (3,'ccc'), (4,'ddd'); +SECURITY LABEL ON TABLE foo_tbl + IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0'; +CREATE TABLE var_tbl(x int, y text); +INSERT INTO var_tbl VALUES (2,'xxx'), (3,'yyy'), (4,'zzz'), (5,'xyz'); +SECURITY LABEL ON TABLE var_tbl + IS 'system_u:object_r:sepgsql_regtest_var_table_t:s0'; -- -- Tests for default labeling behavior -- @@ -99,6 +125,325 @@ SELECT sepgsql_getcon(); -- client's label must be restored unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0 (1 row) +-- +-- Test for Dynamic Domain Transition +-- +-- validation of transaction aware dynamic-transition +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +-------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c25 +(1 row) + +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c15'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +-------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c15 +(1 row) + +SELECT sepgsql_setcon(NULL); -- failed to reset +ERROR: SELinux: security policy violation +SELECT sepgsql_getcon(); + sepgsql_getcon +-------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c15 +(1 row) + +BEGIN; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c12'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +-------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c12 +(1 row) + +SAVEPOINT svpt_1; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c9'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c9 +(1 row) + +SAVEPOINT svpt_2; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c6'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c6 +(1 row) + +SAVEPOINT svpt_3; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c3'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c3 +(1 row) + +ROLLBACK TO SAVEPOINT svpt_2; +SELECT sepgsql_getcon(); -- should be 's0:c0.c9' + sepgsql_getcon +------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c9 +(1 row) + +ROLLBACK TO SAVEPOINT svpt_1; +SELECT sepgsql_getcon(); -- should be 's0:c0.c12' + sepgsql_getcon +-------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c12 +(1 row) + +ABORT; +SELECT sepgsql_getcon(); -- should be 's0:c0.c15' + sepgsql_getcon +-------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c15 +(1 row) + +BEGIN; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c8'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c8 +(1 row) + +SAVEPOINT svpt_1; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c4'); + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c4 +(1 row) + +ROLLBACK TO SAVEPOINT svpt_1; +SELECT sepgsql_getcon(); -- should be 's0:c0.c8' + sepgsql_getcon +------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c8 +(1 row) + +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c6'); + sepgsql_setcon +---------------- + t +(1 row) + +COMMIT; +SELECT sepgsql_getcon(); -- should be 's0:c0.c6' + sepgsql_getcon +------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0:c0.c6 +(1 row) + +-- sepgsql_regtest_user_t is not available dynamic-transition, +-- unless sepgsql_setcon() is called inside of trusted-procedure +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +------------------------------------------------------------ + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15 +(1 row) + +-- sepgsql_regtest_user_t has no permission to switch current label +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0'); -- failed +ERROR: SELinux: security policy violation +SELECT sepgsql_getcon(); + sepgsql_getcon +------------------------------------------------------------ + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15 +(1 row) + +-- trusted procedure allows to switch, but unavailable to override MCS rules +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7'); -- OK + f5 +---- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7 +(1 row) + +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c31'); -- Failed +ERROR: SELinux: security policy violation +CONTEXT: SQL function "f5" statement 1 +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7 +(1 row) + +SELECT f5(NULL); -- Failed +ERROR: SELinux: security policy violation +CONTEXT: SQL function "f5" statement 1 +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7 +(1 row) + +BEGIN; +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c3'); -- OK + f5 +---- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c3 +(1 row) + +ABORT; +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7 +(1 row) + +-- +-- Test for simulation of typical connection pooling server +-- +SELECT sepgsql_getcon(); -- confirm client privilege + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_pool_t:s0 +(1 row) + +-- we shouldn't allow to switch client label without trusted procedure +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_foo_t:s0'); +ERROR: SELinux: security policy violation +SELECT * FROM auth_tbl; -- failed, no permission to reference +ERROR: SELinux: security policy violation +-- switch to "foo" +SELECT auth_func('foo', 'acbd18db4cc2f85cedef654fccc4a4d8'); + auth_func +----------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +---------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_foo_t:s0 +(1 row) + +SELECT * FROM foo_tbl; -- OK + a | b +---+----- + 1 | aaa + 2 | bbb + 3 | ccc + 4 | ddd +(4 rows) + +SELECT * FROM var_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM auth_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT sepgsql_setcon(NULL); -- end of session + sepgsql_setcon +---------------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_pool_t:s0 +(1 row) + +-- the pooler cannot touch these tables directry +SELECT * FROM foo_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM var_tbl; -- failed +ERROR: SELinux: security policy violation +-- switch to "var" +SELECT auth_func('var', 'b2145aac704ce76dbe1ac7adac535b23'); + auth_func +----------- + t +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +---------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_var_t:s0 +(1 row) + +SELECT * FROM foo_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT * FROM var_tbl; -- OK + x | y +---+----- + 2 | xxx + 3 | yyy + 4 | zzz + 5 | xyz +(4 rows) + +SELECT * FROM auth_tbl; -- failed +ERROR: SELinux: security policy violation +SELECT sepgsql_setcon(NULL); -- end of session + sepgsql_setcon +---------------- + t +(1 row) + +-- misc checks +SELECT auth_func('var', 'invalid credential'); -- not works + auth_func +----------- + +(1 row) + +SELECT sepgsql_getcon(); + sepgsql_getcon +----------------------------------------------------- + unconfined_u:unconfined_r:sepgsql_regtest_pool_t:s0 +(1 row) + -- -- Clean up -- @@ -115,3 +460,4 @@ DROP FUNCTION IF EXISTS f1() CASCADE; DROP FUNCTION IF EXISTS f2() CASCADE; DROP FUNCTION IF EXISTS f3() CASCADE; DROP FUNCTION IF EXISTS f4() CASCADE; +DROP FUNCTION IF EXISTS f5(text) CASCADE; diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c index 340bec6864..deadd88b04 100644 --- a/contrib/sepgsql/label.c +++ b/contrib/sepgsql/label.c @@ -12,6 +12,7 @@ #include "access/heapam.h" #include "access/genam.h" +#include "access/xact.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" @@ -27,7 +28,9 @@ #include "miscadmin.h" #include "utils/builtins.h" #include "utils/fmgroids.h" +#include "utils/guc.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/rel.h" #include "utils/tqual.h" @@ -43,16 +46,182 @@ static needs_fmgr_hook_type next_needs_fmgr_hook = NULL; static fmgr_hook_type next_fmgr_hook = NULL; /* - * client_label + * client_label_* * - * security label of the client process + * security label of the database client. Initially the client security label + * is equal to client_label_peer, and can be changed by one or more calls to + * sepgsql_setcon(), and also be temporarily overridden during execution of a + * trusted-procedure. + * + * sepgsql_setcon() is a transaction-aware operation; a (sub-)transaction + * rollback should also rollback the current client security label. Therefore + * we use the list client_label_pending of pending_label to keep track of which + * labels were set during the (sub-)transactions. */ -static char *client_label = NULL; +static char *client_label_peer = NULL; /* set by getpeercon(3) */ +static List *client_label_pending = NIL; /* pending list being set by + * sepgsql_setcon() */ +static char *client_label_committed = NULL; /* set by sepgsql_setcon(), + * and already committed */ +static char *client_label_func = NULL; /* set by trusted procedure */ +typedef struct { + SubTransactionId subid; + char *label; +} pending_label; + +/* + * sepgsql_get_client_label + * + * Returns the current security label of the client. All code should use this + * routine to get the current label, instead of refering to the client_label_* + * variables above. + */ char * sepgsql_get_client_label(void) { - return client_label; + /* trusted procedure client label override */ + if (client_label_func) + return client_label_func; + + /* uncommitted sepgsql_setcon() value */ + if (client_label_pending) + { + pending_label *plabel = llast(client_label_pending); + + if (plabel->label) + return plabel->label; + } + else if (client_label_committed) + return client_label_committed; /* set by sepgsql_setcon() committed */ + + /* default label */ + Assert(client_label_peer != NULL); + return client_label_peer; +} + +/* + * sepgsql_set_client_label + * + * This routine tries to switch the current security label of the client, and + * checks related permissions. The supplied new label shall be added to the + * client_label_pending list, then saved at transaction-commit time to ensure + * transaction-awareness. + */ +static void +sepgsql_set_client_label(const char *new_label) +{ + const char *tcontext; + MemoryContext oldcxt; + pending_label *plabel; + + /* Reset to the initial client label, if NULL */ + if (!new_label) + tcontext = client_label_peer; + else + { + if (security_check_context_raw((security_context_t) new_label) < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_NAME), + errmsg("SELinux: invalid security label: \"%s\"", + new_label))); + tcontext = new_label; + } + + /* Check process:{setcurrent} permission. */ + sepgsql_avc_check_perms_label(sepgsql_get_client_label(), + SEPG_CLASS_PROCESS, + SEPG_PROCESS__SETCURRENT, + NULL, + true); + /* Check process:{dyntransition} permission. */ + sepgsql_avc_check_perms_label(tcontext, + SEPG_CLASS_PROCESS, + SEPG_PROCESS__DYNTRANSITION, + NULL, + true); + /* + * Append the supplied new_label on the pending list until + * the current transaction is committed. + */ + oldcxt = MemoryContextSwitchTo(CurTransactionContext); + + plabel = palloc0(sizeof(pending_label)); + plabel->subid = GetCurrentSubTransactionId(); + if (new_label) + plabel->label = pstrdup(new_label); + client_label_pending = lappend(client_label_pending, plabel); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * sepgsql_xact_callback + * + * A callback routine of transaction commit/abort/prepare. Commmit or abort + * changes in the client_label_pending list. + */ +static void +sepgsql_xact_callback(XactEvent event, void *arg) +{ + if (event == XACT_EVENT_COMMIT) + { + if (client_label_pending != NIL) + { + pending_label *plabel = llast(client_label_pending); + char *new_label; + + if (plabel->label) + new_label = MemoryContextStrdup(TopMemoryContext, + plabel->label); + else + new_label = NULL; + + if (client_label_committed) + pfree(client_label_committed); + + client_label_committed = new_label; + /* + * XXX - Note that items of client_label_pending are allocated + * on CurTransactionContext, thus, all acquired memory region + * shall be released implicitly. + */ + client_label_pending = NIL; + } + } + else if (event == XACT_EVENT_ABORT) + client_label_pending = NIL; +} + +/* + * sepgsql_subxact_callback + * + * A callback routine of sub-transaction start/abort/commit. Releases all + * security labels that are set within the sub-transaction that is aborted. + */ +static void +sepgsql_subxact_callback(SubXactEvent event, SubTransactionId mySubid, + SubTransactionId parentSubid, void *arg) +{ + ListCell *cell; + ListCell *prev; + ListCell *next; + + if (event == SUBXACT_EVENT_ABORT_SUB) + { + prev = NULL; + for (cell = list_head(client_label_pending); cell; cell = next) + { + pending_label *plabel = lfirst(cell); + next = lnext(cell); + + if (plabel->subid == mySubid) + client_label_pending + = list_delete_cell(client_label_pending, cell, prev); + else + prev = cell; + } + } } /* @@ -78,7 +247,7 @@ sepgsql_client_auth(Port *port, int status) /* * Getting security label of the peer process using API of libselinux. */ - if (getpeercon_raw(port->sock, &client_label) < 0) + if (getpeercon_raw(port->sock, &client_label_peer) < 0) ereport(FATAL, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: unable to get peer label: %m"))); @@ -185,8 +354,8 @@ sepgsql_fmgr_hook(FmgrHookEventType event, Assert(!stack->old_label); if (stack->new_label) { - stack->old_label = client_label; - client_label = stack->new_label; + stack->old_label = client_label_func; + client_label_func = stack->new_label; } if (next_fmgr_hook) (*next_fmgr_hook) (event, flinfo, &stack->next_private); @@ -201,7 +370,7 @@ sepgsql_fmgr_hook(FmgrHookEventType event, if (stack->new_label) { - client_label = stack->old_label; + client_label_func = stack->old_label; stack->old_label = NULL; } break; @@ -215,8 +384,8 @@ sepgsql_fmgr_hook(FmgrHookEventType event, /* * sepgsql_init_client_label * - * This routine initialize security label of the client, and set up related - * hooks to be invoked later. + * Initializes the client security label and sets up related hooks for client + * label management. */ void sepgsql_init_client_label(void) @@ -231,7 +400,7 @@ sepgsql_init_client_label(void) * In this case, the process is always hooked on post-authentication, and * we can initialize the sepgsql_mode and client_label correctly. */ - if (getcon_raw(&client_label) < 0) + if (getcon_raw(&client_label_peer) < 0) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("SELinux: failed to get server security label: %m"))); @@ -246,6 +415,10 @@ sepgsql_init_client_label(void) next_fmgr_hook = fmgr_hook; fmgr_hook = sepgsql_fmgr_hook; + + /* Transaction/Sub-transaction callbacks */ + RegisterXactCallback(sepgsql_xact_callback, NULL); + RegisterSubXactCallback(sepgsql_subxact_callback, NULL); } /* @@ -360,6 +533,27 @@ sepgsql_getcon(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text(client_label)); } +/* + * BOOL sepgsql_setcon(TEXT) + * + * It switches the security label of the client. + */ +PG_FUNCTION_INFO_V1(sepgsql_setcon); +Datum +sepgsql_setcon(PG_FUNCTION_ARGS) +{ + const char *new_label; + + if (PG_ARGISNULL(0)) + new_label = NULL; + else + new_label = TextDatumGetCString(PG_GETARG_DATUM(0)); + + sepgsql_set_client_label(new_label); + + PG_RETURN_BOOL(true); +} + /* * TEXT sepgsql_mcstrans_in(TEXT) * diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c index 8819b8cb99..0652e294b7 100644 --- a/contrib/sepgsql/selinux.c +++ b/contrib/sepgsql/selinux.c @@ -45,6 +45,12 @@ static struct { "transition", SEPG_PROCESS__TRANSITION }, + { + "dyntransition", SEPG_PROCESS__DYNTRANSITION + }, + { + "setcurrent", SEPG_PROCESS__SETCURRENT + }, { NULL, 0UL } diff --git a/contrib/sepgsql/sepgsql-regtest.te b/contrib/sepgsql/sepgsql-regtest.te index a8fe2476a4..d872945074 100644 --- a/contrib/sepgsql/sepgsql-regtest.te +++ b/contrib/sepgsql/sepgsql-regtest.te @@ -1,4 +1,4 @@ -policy_module(sepgsql-regtest, 1.03) +policy_module(sepgsql-regtest, 1.04) gen_require(` all_userspace_class_perms @@ -17,6 +17,8 @@ gen_tunable(sepgsql_regression_test_mode, false) # type sepgsql_regtest_trusted_proc_exec_t; postgresql_procedure_object(sepgsql_regtest_trusted_proc_exec_t) +type sepgsql_nosuch_trusted_proc_exec_t; +postgresql_procedure_object(sepgsql_nosuch_trusted_proc_exec_t) # # Test domains for database administrators @@ -35,6 +37,12 @@ optional_policy(` unconfined_rw_pipes(sepgsql_regtest_dba_t) ') +# Type transition rules +allow sepgsql_regtest_dba_t self : process { setcurrent }; +allow sepgsql_regtest_dba_t sepgsql_regtest_user_t : process { dyntransition }; +allow sepgsql_regtest_dba_t sepgsql_regtest_foo_t : process { dyntransition }; +allow sepgsql_regtest_dba_t sepgsql_regtest_var_t : process { dyntransition }; + # # Dummy domain for unpriv users # @@ -51,6 +59,72 @@ optional_policy(` unconfined_stream_connect(sepgsql_regtest_user_t) unconfined_rw_pipes(sepgsql_regtest_user_t) ') +# Type transition rules +allow sepgsql_regtest_user_t sepgsql_regtest_dba_t : process { transition }; +type_transition sepgsql_regtest_user_t sepgsql_regtest_trusted_proc_exec_t:process sepgsql_regtest_dba_t; +type_transition sepgsql_regtest_user_t sepgsql_nosuch_trusted_proc_exec_t:process sepgsql_regtest_nosuch_t; + +# +# Dummy domain for (virtual) connection pooler software +# +# XXX - this test scenario assumes sepgsql_regtest_pool_t domain performs +# as a typical connection pool server; that switches the client label of +# this session prior to any user queries. The sepgsql_regtest_(foo|var)_t +# is allowed to access its own table types, but not allowed to reference +# other's one. +# +role sepgsql_regtest_pool_r; +userdom_base_user_template(sepgsql_regtest_pool) +userdom_manage_home_role(sepgsql_regtest_pool_r, sepgsql_regtest_pool_t) +userdom_exec_user_home_content_files(sepgsql_regtest_pool_t) +userdom_write_user_tmp_sockets(sepgsql_regtest_pool_t) + +type sepgsql_regtest_foo_t; +type sepgsql_regtest_var_t; +type sepgsql_regtest_foo_table_t; +type sepgsql_regtest_var_table_t; + +allow sepgsql_regtest_foo_t sepgsql_regtest_foo_table_t:db_table { getattr select update insert delete lock }; +allow sepgsql_regtest_foo_t sepgsql_regtest_foo_table_t:db_column { getattr select update insert }; +allow sepgsql_regtest_foo_t sepgsql_regtest_foo_table_t:db_tuple { select update insert delete }; + +allow sepgsql_regtest_var_t sepgsql_regtest_var_table_t:db_table { getattr select update insert delete lock }; +allow sepgsql_regtest_var_t sepgsql_regtest_var_table_t:db_column { getattr select update insert }; +allow sepgsql_regtest_var_t sepgsql_regtest_var_table_t:db_tuple { select update insert delete }; + +optional_policy(` + gen_require(` + role unconfined_r; + ') + postgresql_role(unconfined_r, sepgsql_regtest_foo_t) + postgresql_role(unconfined_r, sepgsql_regtest_var_t) + postgresql_table_object(sepgsql_regtest_foo_table_t) + postgresql_table_object(sepgsql_regtest_var_table_t) +') +optional_policy(` + postgresql_stream_connect(sepgsql_regtest_pool_t) + postgresql_role(sepgsql_regtest_pool_r, sepgsql_regtest_pool_t) +') +optional_policy(` + unconfined_stream_connect(sepgsql_regtest_pool_t) + unconfined_rw_pipes(sepgsql_regtest_pool_t) +') +# type transitions +allow sepgsql_regtest_pool_t self:process { setcurrent }; +allow sepgsql_regtest_pool_t sepgsql_regtest_dba_t:process { transition }; +type_transition sepgsql_regtest_pool_t sepgsql_regtest_trusted_proc_exec_t:process sepgsql_regtest_dba_t; + +allow { sepgsql_regtest_foo_t sepgsql_regtest_var_t } self:process { setcurrent }; +allow { sepgsql_regtest_foo_t sepgsql_regtest_var_t } sepgsql_regtest_pool_t:process { dyntransition }; + +# +# Dummy domain for non-exist users +# +role sepgsql_regtest_nosuch_r; +userdom_base_user_template(sepgsql_regtest_nosuch) +optional_policy(` + postgresql_role(sepgsql_regtest_nosuch_r, sepgsql_regtest_nosuch_t) +') # # Rules to launch psql in the dummy domains @@ -62,26 +136,43 @@ optional_policy(` type sepgsql_trusted_proc_t; ') tunable_policy(`sepgsql_regression_test_mode',` - allow unconfined_t sepgsql_regtest_dba_t : process { transition }; - allow unconfined_t sepgsql_regtest_user_t : process { transition }; + allow unconfined_t self : process { setcurrent dyntransition }; + allow unconfined_t sepgsql_regtest_dba_t : process { transition dyntransition }; + allow unconfined_t sepgsql_regtest_user_t : process { transition dyntransition }; + allow unconfined_t sepgsql_regtest_pool_t : process { transition dyntransition }; ') role unconfined_r types sepgsql_regtest_dba_t; role unconfined_r types sepgsql_regtest_user_t; + role unconfined_r types sepgsql_regtest_nosuch_t; role unconfined_r types sepgsql_trusted_proc_t; + + role unconfined_r types sepgsql_regtest_pool_t; + role unconfined_r types sepgsql_regtest_foo_t; + role unconfined_r types sepgsql_regtest_var_t; ') # -# Rule to check +# Rule to execute original trusted procedures +# +# XXX - sepgsql_client_type contains any valid client types, so we allow +# them to execute the original trusted procedure at once. # optional_policy(` - # These rules intends sepgsql_regtest_user_t domain to translate - # sepgsql_regtest_dba_t on execution of procedures labeled as - # sepgsql_regtest_trusted_proc_exec_t, but does not allow transition - # permission from sepgsql_regtest_user_t to sepgsql_regtest_dba_t. - # gen_require(` attribute sepgsql_client_type; ') - allow sepgsql_client_type sepgsql_regtest_trusted_proc_exec_t:db_procedure { getattr execute install }; - type_transition sepgsql_regtest_user_t sepgsql_regtest_trusted_proc_exec_t:process sepgsql_regtest_dba_t; + allow sepgsql_client_type { sepgsql_regtest_trusted_proc_exec_t sepgsql_nosuch_trusted_proc_exec_t }:db_procedure { getattr execute }; + + # These rules intends sepgsql_regtest_user_t domain to translate + # sepgsql_regtest_dba_t on execution of procedures labeled as + # sepgsql_regtest_trusted_proc_exec_t. + # +# allow sepgsql_client_type sepgsql_regtest_trusted_proc_exec_t:db_procedure { getattr execute }; + + # These rules intends sepgsql_regtest_user_t domain to translate + # sepgsql_regtest_nosuch_t on execution of procedures labeled as + # sepgsql_nosuch_trusted_proc_exec_t, without permissions to + # translate to sepgsql_nosuch_trusted_proc_exec_t. + # +# allow sepgsql_client_type sepgsql_nosuch_trusted_proc_exec_t:db_procedure { getattr execute install }; ') diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h index 0100a09d49..708d4ee656 100644 --- a/contrib/sepgsql/sepgsql.h +++ b/contrib/sepgsql/sepgsql.h @@ -57,6 +57,8 @@ * Internally used code of access vectors */ #define SEPG_PROCESS__TRANSITION (1<<0) +#define SEPG_PROCESS__DYNTRANSITION (1<<1) +#define SEPG_PROCESS__SETCURRENT (1<<2) #define SEPG_FILE__READ (1<<0) #define SEPG_FILE__WRITE (1<<1) @@ -274,6 +276,7 @@ extern void sepgsql_object_relabel(const ObjectAddress *object, const char *seclabel); extern Datum sepgsql_getcon(PG_FUNCTION_ARGS); +extern Datum sepgsql_setcon(PG_FUNCTION_ARGS); extern Datum sepgsql_mcstrans_in(PG_FUNCTION_ARGS); extern Datum sepgsql_mcstrans_out(PG_FUNCTION_ARGS); extern Datum sepgsql_restorecon(PG_FUNCTION_ARGS); diff --git a/contrib/sepgsql/sepgsql.sql.in b/contrib/sepgsql/sepgsql.sql.in index 45ffe31e6b..917d12dbbe 100644 --- a/contrib/sepgsql/sepgsql.sql.in +++ b/contrib/sepgsql/sepgsql.sql.in @@ -30,6 +30,7 @@ -- LOAD 'MODULE_PATHNAME'; CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_getcon() RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_getcon' LANGUAGE C; +CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_setcon(text) RETURNS bool AS 'MODULE_PATHNAME', 'sepgsql_setcon' LANGUAGE C; CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_in(text) RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_mcstrans_in' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_mcstrans_out(text) RETURNS text AS 'MODULE_PATHNAME', 'sepgsql_mcstrans_out' LANGUAGE C STRICT; CREATE OR REPLACE FUNCTION pg_catalog.sepgsql_restorecon(text) RETURNS bool AS 'MODULE_PATHNAME', 'sepgsql_restorecon' LANGUAGE C; diff --git a/contrib/sepgsql/sql/label.sql b/contrib/sepgsql/sql/label.sql index 2b1841281c..e63b5f691d 100644 --- a/contrib/sepgsql/sql/label.sql +++ b/contrib/sepgsql/sql/label.sql @@ -31,8 +31,39 @@ CREATE FUNCTION f4 () RETURNS text AS 'SELECT sepgsql_getcon()' LANGUAGE sql; SECURITY LABEL ON FUNCTION f4() + IS 'system_u:object_r:sepgsql_nosuch_trusted_proc_exec_t:s0'; + +CREATE FUNCTION f5 (text) RETURNS bool + AS 'SELECT sepgsql_setcon($1)' + LANGUAGE sql; +SECURITY LABEL ON FUNCTION f5(text) IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0'; +CREATE TABLE auth_tbl(uname text, credential text, label text); +INSERT INTO auth_tbl + VALUES ('foo', 'acbd18db4cc2f85cedef654fccc4a4d8', 'sepgsql_regtest_foo_t:s0'), + ('var', 'b2145aac704ce76dbe1ac7adac535b23', 'sepgsql_regtest_var_t:s0'), + ('baz', 'b2145aac704ce76dbe1ac7adac535b23', 'sepgsql_regtest_baz_t:s0'); +SECURITY LABEL ON TABLE auth_tbl + IS 'system_u:object_r:sepgsql_secret_table_t:s0'; + +CREATE FUNCTION auth_func(text, text) RETURNS bool + LANGUAGE sql + AS 'SELECT sepgsql_setcon(regexp_replace(sepgsql_getcon(), ''_r:.*$'', ''_r:'' || label)) + FROM auth_tbl WHERE uname = $1 AND credential = $2'; +SECURITY LABEL ON FUNCTION auth_func(text,text) + IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0'; + +CREATE TABLE foo_tbl(a int, b text); +INSERT INTO foo_tbl VALUES (1, 'aaa'), (2,'bbb'), (3,'ccc'), (4,'ddd'); +SECURITY LABEL ON TABLE foo_tbl + IS 'system_u:object_r:sepgsql_regtest_foo_table_t:s0'; + +CREATE TABLE var_tbl(x int, y text); +INSERT INTO var_tbl VALUES (2,'xxx'), (3,'yyy'), (4,'zzz'), (5,'xyz'); +SECURITY LABEL ON TABLE var_tbl + IS 'system_u:object_r:sepgsql_regtest_var_table_t:s0'; + -- -- Tests for default labeling behavior -- @@ -68,6 +99,129 @@ SELECT f3(); -- trusted procedure that raises an error SELECT f4(); -- failed on domain transition SELECT sepgsql_getcon(); -- client's label must be restored +-- +-- Test for Dynamic Domain Transition +-- + +-- validation of transaction aware dynamic-transition +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0:c0.c25 +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c15'); +SELECT sepgsql_getcon(); + +SELECT sepgsql_setcon(NULL); -- failed to reset +SELECT sepgsql_getcon(); + +BEGIN; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c12'); +SELECT sepgsql_getcon(); + +SAVEPOINT svpt_1; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c9'); +SELECT sepgsql_getcon(); + +SAVEPOINT svpt_2; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c6'); +SELECT sepgsql_getcon(); + +SAVEPOINT svpt_3; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c3'); +SELECT sepgsql_getcon(); + +ROLLBACK TO SAVEPOINT svpt_2; +SELECT sepgsql_getcon(); -- should be 's0:c0.c9' + +ROLLBACK TO SAVEPOINT svpt_1; +SELECT sepgsql_getcon(); -- should be 's0:c0.c12' + +ABORT; +SELECT sepgsql_getcon(); -- should be 's0:c0.c15' + +BEGIN; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c8'); +SELECT sepgsql_getcon(); + +SAVEPOINT svpt_1; +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c4'); +SELECT sepgsql_getcon(); + +ROLLBACK TO SAVEPOINT svpt_1; +SELECT sepgsql_getcon(); -- should be 's0:c0.c8' +SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c6'); + +COMMIT; +SELECT sepgsql_getcon(); -- should be 's0:c0.c6' + +-- sepgsql_regtest_user_t is not available dynamic-transition, +-- unless sepgsql_setcon() is called inside of trusted-procedure +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15 + +-- sepgsql_regtest_user_t has no permission to switch current label +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0'); -- failed +SELECT sepgsql_getcon(); + +-- trusted procedure allows to switch, but unavailable to override MCS rules +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c7'); -- OK +SELECT sepgsql_getcon(); + +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c31'); -- Failed +SELECT sepgsql_getcon(); + +SELECT f5(NULL); -- Failed +SELECT sepgsql_getcon(); + +BEGIN; +SELECT f5('unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c3'); -- OK +SELECT sepgsql_getcon(); + +ABORT; +SELECT sepgsql_getcon(); + +-- +-- Test for simulation of typical connection pooling server +-- +-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_pool_t:s0 + +-- we shouldn't allow to switch client label without trusted procedure +SELECT sepgsql_setcon('unconfined_u:unconfined_r:sepgsql_regtest_foo_t:s0'); + +SELECT * FROM auth_tbl; -- failed, no permission to reference + +-- switch to "foo" +SELECT auth_func('foo', 'acbd18db4cc2f85cedef654fccc4a4d8'); + +SELECT sepgsql_getcon(); + +SELECT * FROM foo_tbl; -- OK + +SELECT * FROM var_tbl; -- failed + +SELECT * FROM auth_tbl; -- failed + +SELECT sepgsql_setcon(NULL); -- end of session +SELECT sepgsql_getcon(); + +-- the pooler cannot touch these tables directry +SELECT * FROM foo_tbl; -- failed + +SELECT * FROM var_tbl; -- failed + +-- switch to "var" +SELECT auth_func('var', 'b2145aac704ce76dbe1ac7adac535b23'); + +SELECT sepgsql_getcon(); + +SELECT * FROM foo_tbl; -- failed + +SELECT * FROM var_tbl; -- OK + +SELECT * FROM auth_tbl; -- failed + +SELECT sepgsql_setcon(NULL); -- end of session + +-- misc checks +SELECT auth_func('var', 'invalid credential'); -- not works +SELECT sepgsql_getcon(); + -- -- Clean up -- @@ -79,3 +233,4 @@ DROP FUNCTION IF EXISTS f1() CASCADE; DROP FUNCTION IF EXISTS f2() CASCADE; DROP FUNCTION IF EXISTS f3() CASCADE; DROP FUNCTION IF EXISTS f4() CASCADE; +DROP FUNCTION IF EXISTS f5(text) CASCADE; diff --git a/doc/src/sgml/sepgsql.sgml b/doc/src/sgml/sepgsql.sgml index dbddf86bb1..56c465b4e9 100644 --- a/doc/src/sgml/sepgsql.sgml +++ b/doc/src/sgml/sepgsql.sgml @@ -187,7 +187,7 @@ $ cd .../contrib/sepgsql $ make -f /usr/share/selinux/devel/Makefile $ sudo semodule -u sepgsql-regtest.pp $ sudo semodule -l | grep sepgsql -sepgsql-regtest 1.03 +sepgsql-regtest 1.04 @@ -525,6 +525,68 @@ postgres=# SELECT cid, cname, show_credit(cid) FROM customer; + + Dynamic domain transitions + + It is possible to use SELinux's dynamic domain transition feature + to switch the security label of the client process, the client domain, + to a new context, if that is allowed by the security policy. + The client domain needs the 'setcurrent' permission and also + 'dyntransaction' from the old to the new domain. + + + Dynamic domain transitions should be considered carefully, because it + means we allows users to switch their label (also peforms a set of + privileges in SELinux model) in arbitrary way, unlike regular + mandatory way such as trusted procedures. + Thus, The dyntransition permission is only considered safe when used + to switch to a domain with a smaller set of privileges than the + original one, for example: + + +regression=# select sepgsql_getcon(); + sepgsql_getcon +------------------------------------------------------- + unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 +(1 row) + +regression=# SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0-s0:c1.c4'); + sepgsql_setcon +---------------- + t +(1 row) + +regression=# SELECT sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0-s0:c1.c1023'); +ERROR: SELinux: security policy violation + + + In this example above we were allowed to switch from the larger MCS + range c1.c1023 to the smaller range c1.c4, but switching back was + denied. + + + A combination of dynamic domain transition and trusted procedure + enables an interesting use case that fits typical process life- + cycle of connection pooling software. + Even if your connection pooling software is not allowed to run most + of SQL commands, it shall be available to switch the security label + of the client using sepgsql_setcon() function + to be invoked inside of the trusted procedure; that should take some + credential to authorize the request to switch the client label. + After that, this session performs with privileges of the user being + switched, but it shall be unavailable to reference database objects + labeled as other user's one. + Then, it can revert the security label alsp using + sepgsql_setcon() with NULL + argument, unless the security policy prevent it. + The points of this use case are the trusted procedure is only way + for the connection pooling software to switch security label of + the clinet, and the trusted procedure does not work without + appropriate credentials. In addition, it is also a point that the + table to store credentials is only visible from trusted procedure. + + + Miscellaneous @@ -533,6 +595,56 @@ postgres=# SELECT cid, cname, show_credit(cid) FROM customer; + + + + Sepgsql Functions + + shows the available functions. + + + + Sepgsql Functions + + + + sepgsql_getcon() returns text + + Returns the client domain, the current security label of the client. + + + + sepgsql_setcon(text) returns bool + + Switches the client domain of the current session to the new domain, + if allowed by the security policy. + It also accepts NULL input, and it shall be + considered as a transition to the original one. + + + + sepgsql_mcstrans_in(text) returns text + Translates the given qualifies MLS/MCS range into raw format if + the mcstrans daemon is running. + + + + sepgsql_mcstrans_out(text) returns text + Translates the given raw MCS/MCS range into qualified format if + the mcstrans daemon is running. + + + + sepgsql_restorecon(text) returns bool + + Sets up initial security labels for all objectes within the + current database. The argument may be NULL, or the name of a specfile + to be used as alternative of the system default. + + + + +