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. + + + + +