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.
This commit is contained in:
Robert Haas 2012-03-15 16:08:40 -04:00
parent eb990a2b9e
commit 523176cbf1
8 changed files with 931 additions and 23 deletions

View File

@ -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;

View File

@ -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)
*

View File

@ -45,6 +45,12 @@ static struct
{
"transition", SEPG_PROCESS__TRANSITION
},
{
"dyntransition", SEPG_PROCESS__DYNTRANSITION
},
{
"setcurrent", SEPG_PROCESS__SETCURRENT
},
{
NULL, 0UL
}

View File

@ -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 };
')

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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
</screen>
<para>
@ -525,6 +525,68 @@ postgres=# SELECT cid, cname, show_credit(cid) FROM customer;
</para>
</sect3>
<sect3>
<title>Dynamic domain transitions</title>
<para>
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.
</para>
<para>
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:
</para>
<screen>
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
</screen>
<para>
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.
</para>
<para>
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 <literal>sepgsql_setcon()</literal> 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
<literal>sepgsql_setcon()</literal> with <literal>NULL</literal>
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.
</para>
</sect3>
<sect3>
<title>Miscellaneous</title>
<para>
@ -533,6 +595,56 @@ postgres=# SELECT cid, cname, show_credit(cid) FROM customer;
</para>
</sect3>
</sect2>
<sect2 id="sepgsql-functions">
<title>Sepgsql Functions</title>
<para>
<xref linkend="sepgsql-functions-table"> shows the available functions.
</para>
<table id="sepgsql-functions-table">
<title>Sepgsql Functions</title>
<tgroup cols="2">
<tbody>
<row>
<entry><literal>sepgsql_getcon() returns text</literal></entry>
<entry>
Returns the client domain, the current security label of the client.
</entry>
</row>
<row>
<entry><literal>sepgsql_setcon(text) returns bool</literal></entry>
<entry>
Switches the client domain of the current session to the new domain,
if allowed by the security policy.
It also accepts <literal>NULL</literal> input, and it shall be
considered as a transition to the original one.
</entry>
</row>
<row>
<entry><literal>sepgsql_mcstrans_in(text) returns text</literal></entry>
<entry>Translates the given qualifies MLS/MCS range into raw format if
the mcstrans daemon is running.
</entry>
</row>
<row>
<entry><literal>sepgsql_mcstrans_out(text) returns text</literal></entry>
<entry>Translates the given raw MCS/MCS range into qualified format if
the mcstrans daemon is running.
</entry>
</row>
<row>
<entry><literal>sepgsql_restorecon(text) returns bool</literal></entry>
<entry>
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.
</entry>
</row>
</tbody>
</tgroup>
</table>
</sect2>
<sect2 id="sepgsql-limitations">