[v9.2] Add GUC sepgsql.client_label

Started by Kohei KaiGaiabout 14 years ago50 messages
#1Kohei KaiGai
kaigai@kaigai.gr.jp
1 attachment(s)

This patch adds a new GUC "sepgsql.client_label" that allows client
process to switch its privileges into another one, as long as the
system security policy admits this transition.
Because of this feature, I ported two permissions from "process" class
of SELinux; "setcurrent" and "dyntransition". The first one checks
whether the client has a right to switch its privilege. And the other
one checks a particular transition path from X to Y.

This feature might seem to break assumption of the sepgsql's security
model. However, single-directed domain transition from
bigger-privileges to smaller-privileged domain by users' operation is
also supported on operating system, and useful feature to restrict
applications capability at beginning of the session.

A few weeks ago, I got a requirement from Joshua Brindle. He is
working for Web-application that uses CAC (Common Access Card) for its
authentication, and wanted to integrate its security credential and
security label of selinux/sepgsql.
One problem was the system environment unavailable to use
labeled-networking (IPsec), thus, it was not an option to switch the
security label of processes on web-server side. An other solution is
to port dynamic-transition feature into sepgsql, as an analogy of
operating system.

An expected scenario is below:
The web-server is running with WEBSERV domain. It is allowed to
connect to PostgreSQL, and also allowed to invoke an trusted-procedure
that takes an argument of security-credential within CAC, but, nothing
else are allowed.
The trusted-procedure is allowed to reference a table between
security-credential and security-label to be assigned on, then it
switches the security label of client into CLIENT_n.
The CLIENT_n shall be allowed to access tables, functions and others
according to the security policy, and also allowed to reset
"sepgsql.security_label" to revert WEBSERV. However, he is not
available to switch other domain without security-credential stored
within CAC card.

I and Joshua agreed this scenario is reasonable and secure.
So, we'd like to suggest this new feature towards v9.2 timeline.

Thanks,

[*1] CAC - Common Access Card
http://en.wikipedia.org/wiki/Common_Access_Card
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-guc-sepgsql.client_label.v1.patchapplication/octet-stream; name=pgsql-v9.2-guc-sepgsql.client_label.v1.patchDownload
 contrib/sepgsql/expected/label.out |    2 +-
 contrib/sepgsql/expected/misc.out  |  151 ++++++++++++++++++++
 contrib/sepgsql/hooks.c            |  193 +------------------------
 contrib/sepgsql/label.c            |  277 ++++++++++++++++++++++++++++++++++--
 contrib/sepgsql/selinux.c          |    6 +
 contrib/sepgsql/sepgsql-regtest.te |   36 ++++-
 contrib/sepgsql/sepgsql.h          |    4 +-
 contrib/sepgsql/sql/label.sql      |    2 +-
 contrib/sepgsql/sql/misc.sql       |   67 +++++++++
 9 files changed, 531 insertions(+), 207 deletions(-)

diff --git a/contrib/sepgsql/expected/label.out b/contrib/sepgsql/expected/label.out
index bac169f..073ad4f 100644
--- a/contrib/sepgsql/expected/label.out
+++ b/contrib/sepgsql/expected/label.out
@@ -26,7 +26,7 @@ CREATE FUNCTION f4 () RETURNS text
     AS 'SELECT sepgsql_getcon()'
     LANGUAGE sql;
 SECURITY LABEL ON FUNCTION f4()
-    IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0';
+    IS 'system_u:object_r:sepgsql_nosuch_trusted_proc_exec_t:s0';
 --
 -- Tests for default labeling behavior
 --
diff --git a/contrib/sepgsql/expected/misc.out b/contrib/sepgsql/expected/misc.out
index 329852c..3f1213f 100644
--- a/contrib/sepgsql/expected/misc.out
+++ b/contrib/sepgsql/expected/misc.out
@@ -3,3 +3,154 @@
 --
 LOAD '$libdir/sepgsql';		-- failed
 ERROR:  SELinux: LOAD is not permitted
+--
+-- Permission checks related to SET sepgsql.client_label
+--
+-- try to degrade client label
+CREATE OR REPLACE FUNCTION regtest_setcon_1()
+    RETURNS bool LANGUAGE SQL
+    AS 'SET sepgsql.client_label = ''unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c1,c3,c5'';
+        VALUES(true)';
+-- try to upgrade client label
+CREATE OR REPLACE FUNCTION regtest_setcon_2()
+    RETURNS bool LANGUAGE SQL
+    AS 'SET sepgsql.client_label = ''unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c255'';
+        VALUES(true)';
+-- try to reset client label
+CREATE OR REPLACE FUNCTION regtest_resetcon()
+    RETURNS bool LANGUAGE SQL
+    AS 'RESET sepgsql.client_label; VALUES(true)';
+SECURITY LABEL ON FUNCTION regtest_setcon_1()
+		 IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0';
+SECURITY LABEL ON FUNCTION regtest_setcon_2()
+		 IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0';
+SECURITY LABEL ON FUNCTION regtest_resetcon()
+		 IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0';
+SELECT sepgsql_getcon();	-- confirm client privilege
+                  sepgsql_getcon                   
+---------------------------------------------------
+ unconfined_u:unconfined_r:unconfined_t:s0:c0.c255
+(1 row)
+
+SET sepgsql.client_label = 'unconfined_u:unconfined_r:unconfined_t:s0:c0.c15';	-- OK
+SHOW sepgsql.client_label;
+               sepgsql.client_label               
+--------------------------------------------------
+ unconfined_u:unconfined_r:unconfined_t:s0:c0.c15
+(1 row)
+
+RESET sepgsql.client_label;		-- failed
+ERROR:  SELinux: security policy violation
+SHOW sepgsql.client_label;
+               sepgsql.client_label               
+--------------------------------------------------
+ unconfined_u:unconfined_r:unconfined_t:s0:c0.c15
+(1 row)
+
+-- perform as sepgsql_regtest_user_t domai
+SET sepgsql.client_label = 'unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15';	-- OK
+SHOW sepgsql.client_label;
+                    sepgsql.client_label                    
+------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+(1 row)
+
+SET sepgsql.client_label = 'unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0';	-- failed
+ERROR:  SELinux: security policy violation
+SHOW sepgsql.client_label;
+                    sepgsql.client_label                    
+------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+(1 row)
+
+RESET sepgsql.client_label;		-- failed
+ERROR:  SELinux: security policy violation
+SHOW sepgsql.client_label;
+                    sepgsql.client_label                    
+------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+(1 row)
+
+SELECT regtest_setcon_1();		-- OK
+ regtest_setcon_1 
+------------------
+ t
+(1 row)
+
+SHOW sepgsql.client_label;
+                     sepgsql.client_label                     
+--------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c1,c3,c5
+(1 row)
+
+SELECT regtest_setcon_2();		-- failed
+ERROR:  SELinux: security policy violation
+CONTEXT:  SQL function "regtest_setcon_2" statement 1
+SHOW sepgsql.client_label;
+                     sepgsql.client_label                     
+--------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c1,c3,c5
+(1 row)
+
+SELECT regtest_resetcon();		-- failed
+ERROR:  SELinux: security policy violation
+CONTEXT:  SQL function "regtest_resetcon" statement 1
+SHOW sepgsql.client_label;
+                     sepgsql.client_label                     
+--------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c1,c3,c5
+(1 row)
+
+SELECT sepgsql_getcon();	-- confirm client privilege
+                       sepgsql_getcon                       
+------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+(1 row)
+
+SELECT regtest_setcon_1();		-- OK
+ regtest_setcon_1 
+------------------
+ t
+(1 row)
+
+SHOW sepgsql.client_label;
+                     sepgsql.client_label                     
+--------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c1,c3,c5
+(1 row)
+
+SELECT sepgsql_getcon();	-- confirm client privilege
+                       sepgsql_getcon                       
+------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+(1 row)
+
+SELECT regtest_setcon_2();		-- failed
+ERROR:  SELinux: security policy violation
+CONTEXT:  SQL function "regtest_setcon_2" statement 1
+SHOW sepgsql.client_label;
+                    sepgsql.client_label                    
+------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+(1 row)
+
+SELECT sepgsql_getcon();	-- confirm client privilege
+                       sepgsql_getcon                       
+------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+(1 row)
+
+RESET sepgsql.client_label;		-- failed
+ERROR:  SELinux: security policy violation
+SELECT regtest_resetcon();		-- OK
+ regtest_resetcon 
+------------------
+ t
+(1 row)
+
+SHOW sepgsql.client_label;
+                    sepgsql.client_label                    
+------------------------------------------------------------
+ unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+(1 row)
+
diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 47437ba..10a9096 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -18,7 +18,6 @@
 #include "commands/seclabel.h"
 #include "executor/executor.h"
 #include "fmgr.h"
-#include "libpq/auth.h"
 #include "miscadmin.h"
 #include "tcop/utility.h"
 #include "utils/guc.h"
@@ -36,10 +35,7 @@ void		_PG_init(void);
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ClientAuthentication_hook_type next_client_auth_hook = NULL;
 static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
-static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
-static fmgr_hook_type next_fmgr_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
 
@@ -82,48 +78,6 @@ sepgsql_get_debug_audit(void)
 }
 
 /*
- * sepgsql_client_auth
- *
- * Entrypoint of the client authentication hook.
- * It switches the client label according to getpeercon(), and the current
- * performing mode according to the GUC setting.
- */
-static void
-sepgsql_client_auth(Port *port, int status)
-{
-	char	   *context;
-
-	if (next_client_auth_hook)
-		(*next_client_auth_hook) (port, status);
-
-	/*
-	 * In the case when authentication failed, the supplied socket shall be
-	 * closed soon, so we don't need to do anything here.
-	 */
-	if (status != STATUS_OK)
-		return;
-
-	/*
-	 * Getting security label of the peer process using API of libselinux.
-	 */
-	if (getpeercon_raw(port->sock, &context) < 0)
-		ereport(FATAL,
-				(errcode(ERRCODE_INTERNAL_ERROR),
-				 errmsg("SELinux: unable to get peer label: %m")));
-
-	sepgsql_set_client_label(context);
-
-	/*
-	 * Switch the current performing mode from INTERNAL to either DEFAULT or
-	 * PERMISSIVE.
-	 */
-	if (sepgsql_permissive)
-		sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE);
-	else
-		sepgsql_set_mode(SEPGSQL_MODE_DEFAULT);
-}
-
-/*
  * sepgsql_object_access
  *
  * Entrypoint of the object_access_hook. This routine performs as
@@ -221,121 +175,6 @@ sepgsql_exec_check_perms(List *rangeTabls, bool abort)
 }
 
 /*
- * sepgsql_needs_fmgr_hook
- *
- * It informs the core whether the supplied function is trusted procedure,
- * or not. If true, sepgsql_fmgr_hook shall be invoked at start, end, and
- * abort time of function invocation.
- */
-static bool
-sepgsql_needs_fmgr_hook(Oid functionId)
-{
-	ObjectAddress	object;
-
-	if (next_needs_fmgr_hook &&
-		(*next_needs_fmgr_hook) (functionId))
-		return true;
-
-	/*
-	 * SELinux needs the function to be called via security_definer wrapper,
-	 * if this invocation will take a domain-transition. We call these
-	 * functions as trusted-procedure, if the security policy has a rule that
-	 * switches security label of the client on execution.
-	 */
-	if (sepgsql_avc_trusted_proc(functionId) != NULL)
-		return true;
-
-	/*
-	 * Even if not a trusted-procedure, this function should not be inlined
-	 * unless the client has db_procedure:{execute} permission. Please note
-	 * that it shall be actually failed later because of same reason with
-	 * ACL_EXECUTE.
-	 */
-	object.classId = ProcedureRelationId;
-	object.objectId = functionId;
-	object.objectSubId = 0;
-	if (!sepgsql_avc_check_perms(&object,
-								 SEPG_CLASS_DB_PROCEDURE,
-								 SEPG_DB_PROCEDURE__EXECUTE,
-								 SEPGSQL_AVC_NOAUDIT, false))
-		return true;
-
-	return false;
-}
-
-/*
- * sepgsql_fmgr_hook
- *
- * It switches security label of the client on execution of trusted
- * procedures.
- */
-static void
-sepgsql_fmgr_hook(FmgrHookEventType event,
-				  FmgrInfo *flinfo, Datum *private)
-{
-	struct
-	{
-		char	   *old_label;
-		char	   *new_label;
-		Datum		next_private;
-	}		   *stack;
-
-	switch (event)
-	{
-		case FHET_START:
-			stack = (void *) DatumGetPointer(*private);
-			if (!stack)
-			{
-				MemoryContext oldcxt;
-
-				oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt);
-				stack = palloc(sizeof(*stack));
-				stack->old_label = NULL;
-				stack->new_label = sepgsql_avc_trusted_proc(flinfo->fn_oid);
-				stack->next_private = 0;
-
-				MemoryContextSwitchTo(oldcxt);
-
-				/*
-				 * process:transition permission between old and new label,
-				 * when user tries to switch security label of the client
-				 * on execution of trusted procedure.
-				 */
-				if (stack->new_label)
-					sepgsql_avc_check_perms_label(stack->new_label,
-												  SEPG_CLASS_PROCESS,
-												  SEPG_PROCESS__TRANSITION,
-												  NULL, true);
-
-				*private = PointerGetDatum(stack);
-			}
-			Assert(!stack->old_label);
-			if (stack->new_label)
-				stack->old_label = sepgsql_set_client_label(stack->new_label);
-
-			if (next_fmgr_hook)
-				(*next_fmgr_hook) (event, flinfo, &stack->next_private);
-			break;
-
-		case FHET_END:
-		case FHET_ABORT:
-			stack = (void *) DatumGetPointer(*private);
-
-			if (next_fmgr_hook)
-				(*next_fmgr_hook) (event, flinfo, &stack->next_private);
-
-			if (stack->old_label)
-				sepgsql_set_client_label(stack->old_label);
-			stack->old_label = NULL;
-			break;
-
-		default:
-			elog(ERROR, "unexpected event type: %d", (int) event);
-			break;
-	}
-}
-
-/*
  * sepgsql_executor_start
  *
  * It saves contextual information during ExecutorStart to distinguish 
@@ -465,8 +304,6 @@ sepgsql_utility_command(Node *parsetree,
 void
 _PG_init(void)
 {
-	char	   *context;
-
 	/*
 	 * We allow to load the SE-PostgreSQL module on single-user-mode or
 	 * shared_preload_libraries settings only.
@@ -522,33 +359,16 @@ _PG_init(void)
 							 NULL,
 							 NULL);
 
-	/*
-	 * Set up dummy client label.
-	 *
-	 * XXX - note that PostgreSQL launches background worker process like
-	 * autovacuum without authentication steps. So, we initialize sepgsql_mode
-	 * with SEPGSQL_MODE_INTERNAL, and client_label with the security context
-	 * of server process. Later, it also launches background of user session.
-	 * 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(&context) < 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INTERNAL_ERROR),
-				 errmsg("SELinux: failed to get server security label: %m")));
-	sepgsql_set_client_label(context);
-
 	/* Initialize userspace access vector cache */
 	sepgsql_avc_init();
 
+	/* Initialize security label of database client and related hooks */
+	sepgsql_init_client_label();
+
 	/* Security label provider hook */
 	register_label_provider(SEPGSQL_LABEL_TAG,
 							sepgsql_object_relabel);
 
-	/* Client authentication hook */
-	next_client_auth_hook = ClientAuthentication_hook;
-	ClientAuthentication_hook = sepgsql_client_auth;
-
 	/* Object access hook */
 	next_object_access_hook = object_access_hook;
 	object_access_hook = sepgsql_object_access;
@@ -557,13 +377,6 @@ _PG_init(void)
 	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
 	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
 
-	/* Trusted procedure hooks */
-	next_needs_fmgr_hook = needs_fmgr_hook;
-	needs_fmgr_hook = sepgsql_needs_fmgr_hook;
-
-	next_fmgr_hook = fmgr_hook;
-	fmgr_hook = sepgsql_fmgr_hook;
-
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
 	ProcessUtility_hook = sepgsql_utility_command;
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 2ab7a6f..a9ed8b8 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -22,10 +22,12 @@
 #include "catalog/pg_proc.h"
 #include "commands/dbcommands.h"
 #include "commands/seclabel.h"
+#include "libpq/auth.h"
 #include "libpq/libpq-be.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
+#include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/tqual.h"
@@ -35,26 +37,283 @@
 #include <selinux/label.h>
 
 /*
- * client_label
+ * Hooks related to security label of the database client
+ */
+static ClientAuthentication_hook_type next_client_auth_hook = NULL;
+static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
+static fmgr_hook_type next_fmgr_hook = NULL;
+
+/*
+ * client_label_*
  *
- * security label of the client process
+ * security label of the database client; that can be overwritten on the case
+ * when user assigned a particular security label on sepgsql.client_label GUC,
+ * or user executed a trusted procedures.
  */
-static char *client_label = NULL;
+static char *client_label_peer = NULL;
+static char *client_label_guc  = NULL;
+static char *client_label_func = NULL;
 
 char *
 sepgsql_get_client_label(void)
 {
-	return client_label;
+	if (client_label_func)
+		return client_label_func;
+	if (client_label_guc)
+		return client_label_guc;
+	return client_label_peer;
 }
 
-char *
-sepgsql_set_client_label(char *new_label)
+/*
+ * sepgsql_assign_client_label
+ *
+ * It is a callback on assignment of sepgsql.client_label guc; to check
+ * client's capability to set this variable and permission of dynamic domain
+ * transition from current label to the new one.
+ */
+static void
+sepgsql_assign_client_label(const char *newval, void *extra)
+{
+	const char *tcontext;
+
+	/*
+	 * In case of GUC RESET, the client must be allowed to translate from
+	 * the current label to the original one being configured on client
+	 * authentication hook. If no allowed, user cannot revert his domain.
+	 */
+	if (!newval)
+		tcontext = client_label_peer;
+	else
+	{
+		if (security_check_context_raw((security_context_t) newval) < 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_NAME),
+					 errmsg("SELinux: invalid security label: \"%s\"",
+							newval)));
+		tcontext = newval;
+	}
+
+	/*
+	 * 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);
+}
+
+/*
+ * sepgsql_client_auth
+ *
+ * Entrypoint of the client authentication hook.
+ * It switches the client label according to getpeercon(), and the current
+ * performing mode according to the GUC setting.
+ */
+static void
+sepgsql_client_auth(Port *port, int status)
+{
+	if (next_client_auth_hook)
+		(*next_client_auth_hook) (port, status);
+
+	/*
+	 * In the case when authentication failed, the supplied socket shall be
+	 * closed soon, so we don't need to do anything here.
+	 */
+	if (status != STATUS_OK)
+		return;
+
+	/*
+	 * Getting security label of the peer process using API of libselinux.
+	 */
+	if (getpeercon_raw(port->sock, &client_label_peer) < 0)
+		ereport(FATAL,
+				(errcode(ERRCODE_INTERNAL_ERROR),
+				 errmsg("SELinux: unable to get peer label: %m")));
+
+	/*
+	 * Switch the current performing mode from INTERNAL to either DEFAULT or
+	 * PERMISSIVE.
+	 */
+	if (sepgsql_get_permissive())
+		sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE);
+	else
+		sepgsql_set_mode(SEPGSQL_MODE_DEFAULT);
+}
+
+/*
+ * sepgsql_needs_fmgr_hook
+ *
+ * It informs the core whether the supplied function is trusted procedure,
+ * or not. If true, sepgsql_fmgr_hook shall be invoked at start, end, and
+ * abort time of function invocation.
+ */
+static bool
+sepgsql_needs_fmgr_hook(Oid functionId)
+{
+	ObjectAddress	object;
+
+	if (next_needs_fmgr_hook &&
+		(*next_needs_fmgr_hook) (functionId))
+		return true;
+
+	/*
+	 * SELinux needs the function to be called via security_definer wrapper,
+	 * if this invocation will take a domain-transition. We call these
+	 * functions as trusted-procedure, if the security policy has a rule that
+	 * switches security label of the client on execution.
+	 */
+	if (sepgsql_avc_trusted_proc(functionId) != NULL)
+		return true;
+
+	/*
+	 * Even if not a trusted-procedure, this function should not be inlined
+	 * unless the client has db_procedure:{execute} permission. Please note
+	 * that it shall be actually failed later because of same reason with
+	 * ACL_EXECUTE.
+	 */
+	object.classId = ProcedureRelationId;
+	object.objectId = functionId;
+	object.objectSubId = 0;
+	if (!sepgsql_avc_check_perms(&object,
+								 SEPG_CLASS_DB_PROCEDURE,
+								 SEPG_DB_PROCEDURE__EXECUTE,
+								 SEPGSQL_AVC_NOAUDIT, false))
+		return true;
+
+	return false;
+}
+
+/*
+ * sepgsql_fmgr_hook
+ *
+ * It switches security label of the client on execution of trusted
+ * procedures.
+ */
+static void
+sepgsql_fmgr_hook(FmgrHookEventType event,
+				  FmgrInfo *flinfo, Datum *private)
 {
-	char	   *old_label = client_label;
+	struct
+	{
+		char	   *old_label;
+		char	   *new_label;
+		Datum		next_private;
+	}		   *stack;
+
+	switch (event)
+	{
+		case FHET_START:
+			stack = (void *) DatumGetPointer(*private);
+			if (!stack)
+			{
+				MemoryContext oldcxt;
+
+				oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt);
+				stack = palloc(sizeof(*stack));
+				stack->old_label = NULL;
+				stack->new_label = sepgsql_avc_trusted_proc(flinfo->fn_oid);
+				stack->next_private = 0;
+
+				MemoryContextSwitchTo(oldcxt);
+
+				/*
+				 * process:transition permission between old and new label,
+				 * when user tries to switch security label of the client
+				 * on execution of trusted procedure.
+				 */
+				if (stack->new_label)
+					sepgsql_avc_check_perms_label(stack->new_label,
+												  SEPG_CLASS_PROCESS,
+												  SEPG_PROCESS__TRANSITION,
+												  NULL, true);
+
+				*private = PointerGetDatum(stack);
+			}
+			Assert(!stack->old_label);
+			if (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);
+			break;
+
+		case FHET_END:
+		case FHET_ABORT:
+			stack = (void *) DatumGetPointer(*private);
+
+			if (next_fmgr_hook)
+				(*next_fmgr_hook) (event, flinfo, &stack->next_private);
 
-	client_label = new_label;
+			if (stack->new_label)
+			{
+				client_label_func = stack->old_label;
+				stack->old_label = NULL;
+			}
+			break;
 
-	return old_label;
+		default:
+			elog(ERROR, "unexpected event type: %d", (int) event);
+			break;
+	}
+}
+
+/*
+ * sepgsql_init_client_label
+ *
+ * It initializes GUC variable on security label of the database client,
+ * then set up hooks related to client authentication and trusted procedures.
+ */
+void
+sepgsql_init_client_label(void)
+{
+	/*
+	 * Set up dummy client label.
+	 *
+	 * XXX - note that PostgreSQL launches background worker process like
+	 * autovacuum without authentication steps. So, we initialize sepgsql_mode
+	 * with SEPGSQL_MODE_INTERNAL, and client_label with the security context
+	 * of server process. Later, it also launches background of user session.
+	 * 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_peer) < 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INTERNAL_ERROR),
+				 errmsg("SELinux: failed to get server security label: %m")));
+
+	DefineCustomStringVariable("sepgsql.client_label",
+							   "security label of the database client",
+							   NULL,
+							   &client_label_guc,
+							   NULL,
+							   PGC_USERSET,
+							   GUC_NOT_IN_SAMPLE,
+							   NULL,
+							   sepgsql_assign_client_label,
+							   (GucShowHook) sepgsql_get_client_label);
+
+	/* Client authentication hook */
+	next_client_auth_hook = ClientAuthentication_hook;
+	ClientAuthentication_hook = sepgsql_client_auth;
+
+	/* Trusted procedure hooks */
+	next_needs_fmgr_hook = needs_fmgr_hook;
+	needs_fmgr_hook = sepgsql_needs_fmgr_hook;
+
+	next_fmgr_hook = fmgr_hook;
+	fmgr_hook = sepgsql_fmgr_hook;
 }
 
 /*
diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c
index 8819b8c..0652e29 100644
--- a/contrib/sepgsql/selinux.c
+++ b/contrib/sepgsql/selinux.c
@@ -46,6 +46,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 a8fe247..1d489cd 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
@@ -53,6 +55,15 @@ optional_policy(`
 ')
 
 #
+# 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
 #
 optional_policy(`
@@ -62,11 +73,13 @@ 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};
 	')
 	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;
 ')
 
@@ -76,12 +89,25 @@ optional_policy(`
 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.
+	# sepgsql_regtest_trusted_proc_exec_t.
+	# And, also allows to translate from sepgsql_regtest_dba_t to
+	# sepgsql_regtest_user_t via SET sepgsql.client_label.
 	#
 	gen_require(`
 		attribute sepgsql_client_type;
 	')
 	allow sepgsql_client_type sepgsql_regtest_trusted_proc_exec_t:db_procedure { getattr execute install };
+	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;
+
+	allow sepgsql_regtest_dba_t self : process { setcurrent };
+	allow sepgsql_regtest_dba_t sepgsql_regtest_user_t : process { dyntransition };
+
+	# 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 };
+	type_transition sepgsql_regtest_user_t sepgsql_nosuch_trusted_proc_exec_t:process sepgsql_regtest_nosuch_t;
 ')
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index c93da7a..c97488a 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)
@@ -267,7 +269,7 @@ extern void sepgsql_avc_init(void);
  * label.c
  */
 extern char *sepgsql_get_client_label(void);
-extern char *sepgsql_set_client_label(char *new_label);
+extern void  sepgsql_init_client_label(void);
 extern char *sepgsql_get_label(Oid relOid, Oid objOid, int32 subId);
 
 extern void sepgsql_object_relabel(const ObjectAddress *object,
diff --git a/contrib/sepgsql/sql/label.sql b/contrib/sepgsql/sql/label.sql
index 2b18412..6a9df77 100644
--- a/contrib/sepgsql/sql/label.sql
+++ b/contrib/sepgsql/sql/label.sql
@@ -31,7 +31,7 @@ CREATE FUNCTION f4 () RETURNS text
     AS 'SELECT sepgsql_getcon()'
     LANGUAGE sql;
 SECURITY LABEL ON FUNCTION f4()
-    IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0';
+    IS 'system_u:object_r:sepgsql_nosuch_trusted_proc_exec_t:s0';
 
 --
 -- Tests for default labeling behavior
diff --git a/contrib/sepgsql/sql/misc.sql b/contrib/sepgsql/sql/misc.sql
index a46d8a6..2beae52 100644
--- a/contrib/sepgsql/sql/misc.sql
+++ b/contrib/sepgsql/sql/misc.sql
@@ -3,3 +3,70 @@
 --
 
 LOAD '$libdir/sepgsql';		-- failed
+
+
+--
+-- Permission checks related to SET sepgsql.client_label
+--
+
+-- try to degrade client label
+CREATE OR REPLACE FUNCTION regtest_setcon_1()
+    RETURNS bool LANGUAGE SQL
+    AS 'SET sepgsql.client_label = ''unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c1,c3,c5'';
+        VALUES(true)';
+-- try to upgrade client label
+CREATE OR REPLACE FUNCTION regtest_setcon_2()
+    RETURNS bool LANGUAGE SQL
+    AS 'SET sepgsql.client_label = ''unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c255'';
+        VALUES(true)';
+-- try to reset client label
+CREATE OR REPLACE FUNCTION regtest_resetcon()
+    RETURNS bool LANGUAGE SQL
+    AS 'RESET sepgsql.client_label; VALUES(true)';
+
+SECURITY LABEL ON FUNCTION regtest_setcon_1()
+		 IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0';
+SECURITY LABEL ON FUNCTION regtest_setcon_2()
+		 IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0';
+SECURITY LABEL ON FUNCTION regtest_resetcon()
+		 IS 'system_u:object_r:sepgsql_regtest_trusted_proc_exec_t:s0';
+
+-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0:c0.c255
+SET sepgsql.client_label = 'unconfined_u:unconfined_r:unconfined_t:s0:c0.c15';	-- OK
+SHOW sepgsql.client_label;
+
+RESET sepgsql.client_label;		-- failed
+SHOW sepgsql.client_label;
+
+-- perform as sepgsql_regtest_user_t domain
+SET sepgsql.client_label = 'unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15';	-- OK
+SHOW sepgsql.client_label;
+
+SET sepgsql.client_label = 'unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0';	-- failed
+SHOW sepgsql.client_label;
+
+RESET sepgsql.client_label;		-- failed
+SHOW sepgsql.client_label;
+
+SELECT regtest_setcon_1();		-- OK
+SHOW sepgsql.client_label;
+
+SELECT regtest_setcon_2();		-- failed
+SHOW sepgsql.client_label;
+
+SELECT regtest_resetcon();		-- failed
+SHOW sepgsql.client_label;
+
+-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+SELECT regtest_setcon_1();		-- OK
+SHOW sepgsql.client_label;
+
+-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+SELECT regtest_setcon_2();		-- failed
+SHOW sepgsql.client_label;
+
+-- @SECURITY-CONTEXT=unconfined_u:unconfined_r:sepgsql_regtest_user_t:s0:c0.c15
+RESET sepgsql.client_label;		-- failed
+
+SELECT regtest_resetcon();		-- OK
+SHOW sepgsql.client_label;
#2Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#1)
Re: [v9.2] Add GUC sepgsql.client_label

On Tue, Jan 10, 2012 at 6:28 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

This patch adds a new GUC "sepgsql.client_label" that allows client
process to switch its privileges into another one, as long as the
system security policy admits this transition.
Because of this feature, I ported two permissions from "process" class
of SELinux; "setcurrent" and "dyntransition". The first one checks
whether the client has a right to switch its privilege. And the other
one checks a particular transition path from X to Y.

This feature might seem to break assumption of the sepgsql's security
model. However, single-directed domain transition from
bigger-privileges to smaller-privileged domain by users' operation is
also supported on operating system, and useful feature to restrict
applications capability at beginning of the session.

A few weeks ago, I got a requirement from Joshua Brindle. He is
working for Web-application that uses CAC (Common Access Card) for its
authentication, and wanted to integrate its security credential and
security label of selinux/sepgsql.
One problem was the system environment unavailable to use
labeled-networking (IPsec), thus, it was not an option to switch the
security label of processes on web-server side. An other solution is
to port dynamic-transition feature into sepgsql, as an analogy of
operating system.

An expected scenario is below:
The web-server is running with WEBSERV domain. It is allowed to
connect to PostgreSQL, and also allowed to invoke an trusted-procedure
that takes an argument of security-credential within CAC, but, nothing
else are allowed.
The trusted-procedure is allowed to reference a table between
security-credential and security-label to be assigned on, then it
switches the security label of client into CLIENT_n.
The CLIENT_n shall be allowed to access tables, functions and others
according to the security policy, and also allowed to reset
"sepgsql.security_label" to revert WEBSERV. However, he is not
available to switch other domain without security-credential stored
within CAC card.

I and Joshua agreed this scenario is reasonable and secure.
So, we'd like to suggest this new feature towards v9.2 timeline.

I'm wondering if a function would be a better fit than a GUC. I don't
think you can really restrict the ability to revert a GUC change -
i.e. if someone does a SET and then a RESET, you pretty much have to
allow that. I think. But if you expose a function then it can work
however you like.

On another note, this is an awfully large patch. Is there a separate
patch here that just does code rearrangement that should be separated
out?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#3Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#2)
Re: [v9.2] Add GUC sepgsql.client_label

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

I'm wondering if a function would be a better fit than a GUC.  I don't
think you can really restrict the ability to revert a GUC change -
i.e. if someone does a SET and then a RESET, you pretty much have to
allow that.  I think.  But if you expose a function then it can work
however you like.

One benefit to use GUC is that we can utilize existing mechanism to
revert a value set within a transaction block on error.
If we implement same functionality with functions, XactCallback allows
sepgsql to get control on appropriate timing?

On another note, this is an awfully large patch.  Is there a separate
patch here that just does code rearrangement that should be separated
out?

OK. I moved some routines related to client_label into label.c.
I'll separate this part from the new functionality part.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#4Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#3)
Re: [v9.2] Add GUC sepgsql.client_label

On Thu, Jan 26, 2012 at 2:07 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

I'm wondering if a function would be a better fit than a GUC.  I don't
think you can really restrict the ability to revert a GUC change -
i.e. if someone does a SET and then a RESET, you pretty much have to
allow that.  I think.  But if you expose a function then it can work
however you like.

One benefit to use GUC is that we can utilize existing mechanism to
revert a value set within a transaction block on error.
If we implement same functionality with functions, XactCallback allows
sepgsql to get control on appropriate timing?

Not sure, but I thought the use case was to set this at connection
startup time and then hand the connection off to a client. What keeps
the client from just issuing RESET?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#5Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#4)
Re: [v9.2] Add GUC sepgsql.client_label

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

On Thu, Jan 26, 2012 at 2:07 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

I'm wondering if a function would be a better fit than a GUC.  I don't
think you can really restrict the ability to revert a GUC change -
i.e. if someone does a SET and then a RESET, you pretty much have to
allow that.  I think.  But if you expose a function then it can work
however you like.

One benefit to use GUC is that we can utilize existing mechanism to
revert a value set within a transaction block on error.
If we implement same functionality with functions, XactCallback allows
sepgsql to get control on appropriate timing?

Not sure, but I thought the use case was to set this at connection
startup time and then hand the connection off to a client.  What keeps
the client from just issuing RESET?

In the use case of Joshua, the original security label does not privileges
to reference any tables, and it cannot translate any domains without
credential within IC-cards. Thus, RESET is harmless.

However, I also assume one other possible use-case; the originator
has privileges to translate 10 of different domains, but disallows to
revert it. In this case, RESET without permission checks are harmful.
(This scenario will be suitable with multi-category-model.)

Apart from this issue, I found a problem on implementation using GUC
options. It makes backend crash, if it tries to reset sepgsql.client_label
without permissions within error handler because of double-errors.

So, I'll try to modify the patch with an idea to use a function.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#6Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#5)
Re: [v9.2] Add GUC sepgsql.client_label

2012/1/28 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

On Thu, Jan 26, 2012 at 2:07 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

I'm wondering if a function would be a better fit than a GUC.  I don't
think you can really restrict the ability to revert a GUC change -
i.e. if someone does a SET and then a RESET, you pretty much have to
allow that.  I think.  But if you expose a function then it can work
however you like.

One benefit to use GUC is that we can utilize existing mechanism to
revert a value set within a transaction block on error.
If we implement same functionality with functions, XactCallback allows
sepgsql to get control on appropriate timing?

Not sure, but I thought the use case was to set this at connection
startup time and then hand the connection off to a client.  What keeps
the client from just issuing RESET?

In the use case of Joshua, the original security label does not privileges
to reference any tables, and it cannot translate any domains without
credential within IC-cards. Thus, RESET is harmless.

However, I also assume one other possible use-case; the originator
has privileges to translate 10 of different domains, but disallows to
revert it. In this case, RESET without permission checks are harmful.
(This scenario will be suitable with multi-category-model.)

Apart from this issue, I found a problem on implementation using GUC
options. It makes backend crash, if it tries to reset sepgsql.client_label
without permissions within error handler because of double-errors.

I found an idea to avoid this scenario.

The problematic case is reset GUC variable because of transaction
rollback and permission violation, however, we don't intend to apply
permission checks, since it is not committed yet.
Thus, I considered to check state of the transaction using
IsTransactionState() on the assign_hook of GUC variable.

Unlike function based implementation, it is available to utilize existing
infrastructure to set the client_label to be transaction-aware.

It shall be implemented as:

void
sepgsql_assign_client_label(const char *newval, void *extra)
{
if (!IsTransactionState)
return;

/* check whether valid security context */

/* check permission check to switch current context */
}

How about such an implementation?

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#7Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#6)
Re: [v9.2] Add GUC sepgsql.client_label

On Sun, Jan 29, 2012 at 7:27 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/1/28 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

On Thu, Jan 26, 2012 at 2:07 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

I'm wondering if a function would be a better fit than a GUC.  I don't
think you can really restrict the ability to revert a GUC change -
i.e. if someone does a SET and then a RESET, you pretty much have to
allow that.  I think.  But if you expose a function then it can work
however you like.

One benefit to use GUC is that we can utilize existing mechanism to
revert a value set within a transaction block on error.
If we implement same functionality with functions, XactCallback allows
sepgsql to get control on appropriate timing?

Not sure, but I thought the use case was to set this at connection
startup time and then hand the connection off to a client.  What keeps
the client from just issuing RESET?

In the use case of Joshua, the original security label does not privileges
to reference any tables, and it cannot translate any domains without
credential within IC-cards. Thus, RESET is harmless.

However, I also assume one other possible use-case; the originator
has privileges to translate 10 of different domains, but disallows to
revert it. In this case, RESET without permission checks are harmful.
(This scenario will be suitable with multi-category-model.)

Apart from this issue, I found a problem on implementation using GUC
options. It makes backend crash, if it tries to reset sepgsql.client_label
without permissions within error handler because of double-errors.

I found an idea to avoid this scenario.

The problematic case is reset GUC variable because of transaction
rollback and permission violation, however, we don't intend to apply
permission checks, since it is not committed yet.
Thus, I considered to check state of the transaction using
IsTransactionState() on the assign_hook of GUC variable.

Unlike function based implementation, it is available to utilize existing
infrastructure to set the client_label to be transaction-aware.

It shall be implemented as:

void
sepgsql_assign_client_label(const char *newval, void *extra)
{
   if (!IsTransactionState)
       return;

   /* check whether valid security context */

   /* check permission check to switch current context */
}

How about such an implementation?

Well, I think the idea that GUC changes can be reverted at any time is
fairly deeply ingrained in the GUC machinery. So I think that's a
hack: it might happen to work today (or at least on the cases we can
think of to test), but I wouldn't be a bit surprised if there are
other cases where it fails, either now or in the future. I don't see
any good reason to assume that unrevertable changes are OK even inside
a transaction context. For example, if someone applies a context to a
function with ALTER FUNCTION, they're going to expect that the altered
context remains in effect while the function is running and gets
reverted on exit. If you throw an error when the system tries to
revert the change at function-exit time, it might not crash the
server, but it's certainly going to violate the principle of least
astonishment.

Of course, we already have a trusted procedure mechanism which is
intended to support temporary changes to the effective security label,
so you might say, hey, people shouldn't do that. But they might. And
I wouldn't like to bet that's the only case that could be problematic
either. What about a setting in postgresql.conf? You could end up
being asked to set the security label to some other value before
you've initialized it. What about SET LOCAL? It's not OK to fail to
revert that at transaction exit.

Hence my suggestion of a function: if you use functions, you can
implement whatever semantics you want.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#8Yeb Havinga
yebhavinga@gmail.com
In reply to: Robert Haas (#7)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-01-30 19:19, Robert Haas wrote:

On Sun, Jan 29, 2012 at 7:27 AM, Kohei KaiGai<kaigai@kaigai.gr.jp> wrote:

However, I also assume one other possible use-case; the originator
has privileges to translate 10 of different domains, but disallows to
revert it. In this case, RESET without permission checks are harmful.
(This scenario will be suitable with multi-category-model.)

Of course, we already have a trusted procedure mechanism which is
intended to support temporary changes to the effective security label,
so you might say, hey, people shouldn't do that. But they might. And I
wouldn't like to bet that's the only case that could be problematic
either. What about a setting in postgresql.conf? You could end up
being asked to set the security label to some other value before
you've initialized it. What about SET LOCAL? It's not OK to fail to
revert that at transaction exit. Hence my suggestion of a function: if
you use functions, you can implement whatever semantics you want.

What about always allowing a transition to the default / postgresql.conf
configured client label, so in the case of errors, or RESET, the
transition to this domain is hardcoded to succeed. This would also be
useful in conjunction with a connection pooler. This would still allow
the option to prevent a back transition to non-default client labels.

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#9Robert Haas
robertmhaas@gmail.com
In reply to: Yeb Havinga (#8)
Re: [v9.2] Add GUC sepgsql.client_label

On Tue, Jan 31, 2012 at 4:36 AM, Yeb Havinga <yebhavinga@gmail.com> wrote:

What about always allowing a transition to the default / postgresql.conf
configured client label, so in the case of errors, or RESET, the transition
to this domain is hardcoded to succeed. This would also be useful in
conjunction with a connection pooler. This would still allow the option to
prevent a back transition to non-default client labels.

I don't think you can make that work, because someone can still
attempt to RESET to a different value, and it's still not safe to make
that fail.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#10Yeb Havinga
yebhavinga@gmail.com
In reply to: Robert Haas (#9)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-01-31 14:06, Robert Haas wrote:

On Tue, Jan 31, 2012 at 4:36 AM, Yeb Havinga<yebhavinga@gmail.com> wrote:

What about always allowing a transition to the default / postgresql.conf
configured client label, so in the case of errors, or RESET, the transition
to this domain is hardcoded to succeed. This would also be useful in
conjunction with a connection pooler. This would still allow the option to
prevent a back transition to non-default client labels.

I don't think you can make that work, because someone can still
attempt to RESET to a different value, and it's still not safe to make
that fail.

But the idea is that if that different value is a (possibly changed into
a new) allowed default value, a RESET to that new default value will be
allowed, by definition, because it is a default value.

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#11Robert Haas
robertmhaas@gmail.com
In reply to: Yeb Havinga (#10)
Re: [v9.2] Add GUC sepgsql.client_label

On Tue, Jan 31, 2012 at 9:10 AM, Yeb Havinga <yebhavinga@gmail.com> wrote:

On 2012-01-31 14:06, Robert Haas wrote:

On Tue, Jan 31, 2012 at 4:36 AM, Yeb Havinga<yebhavinga@gmail.com>  wrote:

What about always allowing a transition to the default / postgresql.conf
configured client label, so in the case of errors, or RESET, the
transition
to this domain is hardcoded to succeed. This would also be useful in
conjunction with a connection pooler. This would still allow the option
to
prevent a back transition to non-default client labels.

I don't think you can make that work, because someone can still
attempt to RESET to a different value, and it's still not safe to make
that fail.

But the idea is that if that different value is a (possibly changed into a
new) allowed default value, a RESET to that new default value will be
allowed, by definition, because it is a default value.

*scratches head*

I'm not sure I follow you. If you're saying that we can make this
work by always allowing the value to be reset, then I agree with you,
but I'm not sure those are the semantics KaiGai wants. For instance,
if a connection pooler does:

SET sepgsql.client_label = 'bob_t';

...and then hands off to the client, the client can then do:

RESET sepgsql.client_label;
SET sepgsql.client_label = 'alice_t';

....and that's bad. More generally, the system security policy is
designed to answer questions about whether it's OK to transition from
A->B, and the fact that A->B is OK does not mean that B->A is OK, but
our GUC mechanism pretty much forces you to allow both of those
things, or neither.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#12Yeb Havinga
yebhavinga@gmail.com
In reply to: Robert Haas (#11)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-01-31 15:28, Robert Haas wrote:

*scratches head*

I'm not sure I follow you. If you're saying that we can make this
work by always allowing the value to be reset, then I agree with you,
but I'm not sure those are the semantics KaiGai wants. For instance,
if a connection pooler does:

SET sepgsql.client_label = 'bob_t';

...and then hands off to the client, the client can then do:

RESET sepgsql.client_label;
SET sepgsql.client_label = 'alice_t';

....and that's bad.

Hmm yes this is a problem. Reading the original post better, it is also
not the intended behaviour to support repeatable client_label switches.

"However, single-directed domain transition from bigger-privileges to
smaller-privileged domain by users' operation is also supported on
operating system, and useful feature to restrict applications capability
at beginning of the session."

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#11)
Re: [v9.2] Add GUC sepgsql.client_label

Robert Haas <robertmhaas@gmail.com> writes:

....and that's bad. More generally, the system security policy is
designed to answer questions about whether it's OK to transition from
A->B, and the fact that A->B is OK does not mean that B->A is OK, but
our GUC mechanism pretty much forces you to allow both of those
things, or neither.

More to the point, a GUC rollback transition *has to always succeed*.
Period. Now, the value that it's trying to roll back to was presumably
considered legitimate at some previous time, but if you're designing a
system that is based purely on state transitions it could very well see
the rollback transition as invalid. That is just going to be too
fragile to be acceptable.

I think that this will have to be set up so that it understands the
difference between a forward transition and a rollback and only checks
the former. If that's not possible, this is not going to get in.

regards, tom lane

#14Kevin Grittner
Kevin.Grittner@wicourts.gov
In reply to: Tom Lane (#13)
Re: [v9.2] Add GUC sepgsql.client_label

Tom Lane wrote:

More to the point, a GUC rollback transition *has to always
succeed*. Period.

I was about to point out the exception of the transaction_read_only
GUC, which according to the standard must not be changed except at
the beginning of a transaction or a subtransaction, and must not be
changed from "on" to "off" in a subtransaction. Then I noticed that,
while we protect against an explicit change in a prohibited way, we
allow a RESET:

test=# begin transaction read only;
BEGIN
test=# select * from x;
x
---
1
(1 row)

test=# set transaction_read_only = off;
ERROR: transaction read-write mode must be set before any query
test=# rollback;
ROLLBACK
test=# begin transaction read only;
BEGIN
test=# select * from x;
x
---
1
(1 row)

test=# reset transaction_read_only ;
RESET
test=# insert into x VALUES (2);
INSERT 0 1
test=# commit;
COMMIT

I think that's a problem. It could allow back-door violations of
invariants enforced by triggers, and seems to violate the SQL
standard. I think this should be considered a bug, although I'm not
sure whether it's safe to back-patch, given the change to existing
behavior.

Whether such a (required) exception to what you assert above should
open the door to any others is another question.

-Kevin

#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kevin Grittner (#14)
Re: [v9.2] Add GUC sepgsql.client_label

"Kevin Grittner" <Kevin.Grittner@wicourts.gov> writes:

Tom Lane wrote:

More to the point, a GUC rollback transition *has to always
succeed*. Period.

[ counterexample showing we should sometimes disallow "RESET" ]

This actually isn't what I was talking about: a RESET statement is a
commanded adoption of a new value that happens to be gotten off the
stack, and there's not a lot of logical difficulty in letting it fail.
What I was concerned about was the case where GUC is trying to
re-establish an old value during transaction rollback. For example,
assume we are superuser to start with, and we do

begin;
set role unprivileged_user;
...
rollback;

The rollback needs to transition the role setting back to superuser.
A check based purely on allowed transitions would disallow that, since
it's not visibly different from the unprivileged_user trying to make
himself superuser. You have to take the context of the state change
into account.

And yeah, I agree it's a bug that you can do what Kevin's example shows.

regards, tom lane

#16Kevin Grittner
Kevin.Grittner@wicourts.gov
In reply to: Tom Lane (#15)
Re: [v9.2] Add GUC sepgsql.client_label

Tom Lane wrote:

What I was concerned about was the case where GUC is trying to
re-establish an old value during transaction rollback. For example,
assume we are superuser to start with, and we do

begin;
set role unprivileged_user;
...
rollback;

The rollback needs to transition the role setting back to
superuser.

Sorry for missing the point. Yeah, I totally agree with that.

And yeah, I agree it's a bug that you can do what Kevin's example
shows.

I'll look at it and see if I can pull together a patch.

-Kevin

#17Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#7)
2 attachment(s)
Re: [v9.2] Add GUC sepgsql.client_label

2012/1/30 Robert Haas <robertmhaas@gmail.com>:

On Sun, Jan 29, 2012 at 7:27 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/1/28 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

On Thu, Jan 26, 2012 at 2:07 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/1/26 Robert Haas <robertmhaas@gmail.com>:

I'm wondering if a function would be a better fit than a GUC.  I don't
think you can really restrict the ability to revert a GUC change -
i.e. if someone does a SET and then a RESET, you pretty much have to
allow that.  I think.  But if you expose a function then it can work
however you like.

One benefit to use GUC is that we can utilize existing mechanism to
revert a value set within a transaction block on error.
If we implement same functionality with functions, XactCallback allows
sepgsql to get control on appropriate timing?

Not sure, but I thought the use case was to set this at connection
startup time and then hand the connection off to a client.  What keeps
the client from just issuing RESET?

In the use case of Joshua, the original security label does not privileges
to reference any tables, and it cannot translate any domains without
credential within IC-cards. Thus, RESET is harmless.

However, I also assume one other possible use-case; the originator
has privileges to translate 10 of different domains, but disallows to
revert it. In this case, RESET without permission checks are harmful.
(This scenario will be suitable with multi-category-model.)

Apart from this issue, I found a problem on implementation using GUC
options. It makes backend crash, if it tries to reset sepgsql.client_label
without permissions within error handler because of double-errors.

I found an idea to avoid this scenario.

The problematic case is reset GUC variable because of transaction
rollback and permission violation, however, we don't intend to apply
permission checks, since it is not committed yet.
Thus, I considered to check state of the transaction using
IsTransactionState() on the assign_hook of GUC variable.

Unlike function based implementation, it is available to utilize existing
infrastructure to set the client_label to be transaction-aware.

It shall be implemented as:

void
sepgsql_assign_client_label(const char *newval, void *extra)
{
   if (!IsTransactionState)
       return;

   /* check whether valid security context */

   /* check permission check to switch current context */
}

How about such an implementation?

Well, I think the idea that GUC changes can be reverted at any time is
fairly deeply ingrained in the GUC machinery.  So I think that's a
hack: it might happen to work today (or at least on the cases we can
think of to test), but I wouldn't be a bit surprised if there are
other cases where it fails, either now or in the future.  I don't see
any good reason to assume that unrevertable changes are OK even inside
a transaction context.  For example, if someone applies a context to a
function with ALTER FUNCTION, they're going to expect that the altered
context remains in effect while the function is running and gets
reverted on exit.  If you throw an error when the system tries to
revert the change at function-exit time, it might not crash the
server, but it's certainly going to violate the principle of least
astonishment.

Of course, we already have a trusted procedure mechanism which is
intended to support temporary changes to the effective security label,
so you might say, hey, people shouldn't do that.  But they might.  And
I wouldn't like to bet that's the only case that could be problematic
either.  What about a setting in postgresql.conf?  You could end up
being asked to set the security label to some other value before
you've initialized it.  What about SET LOCAL?  It's not OK to fail to
revert that at transaction exit.

Hence my suggestion of a function: if you use functions, you can
implement whatever semantics you want.

I tried to implement it based on the function approach.

The new sepgsql_setcon(TEXT) enables to set and reset alternative
security label of the database client, unless it violates permission
checks.

The supplied alternative security label is once appended on the
client_label_pending list, then an active item shall be saved on
the transaction commit time. If sub-transaction is rollbacked to
a particular savepoint, items on the pending-list shall be removed
according to its sub-transaction-id.

It extends idea of client_label into three types; peer-label, setcon-label
and func-label. The first one is obtained via getpeercon(3) on
database logon time, and always not null. The second one is set
using the new sepgsql_setcon(), and the other one is set during
execution of trusted procedure.

Thus, the logic of sepgsql_get_client_label() were modified to handle
these three kind of client labels correctly.

The attached part-1 patch moves related routines from hooks.c to
label.c because of references to static variables.
The part-2 patch implements above mechanism.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-sepgsql-setcon.part-2.v2.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-setcon.part-2.v2.patchDownload
 contrib/sepgsql/expected/label.out |  223 ++++++++++++++++++++++++++++++++++++
 contrib/sepgsql/label.c            |  212 ++++++++++++++++++++++++++++++++--
 contrib/sepgsql/selinux.c          |    6 +
 contrib/sepgsql/sepgsql-regtest.te |   36 +++++-
 contrib/sepgsql/sepgsql.h          |    3 +
 contrib/sepgsql/sql/label.sql      |   84 ++++++++++++++
 6 files changed, 550 insertions(+), 14 deletions(-)

diff --git a/contrib/sepgsql/expected/label.out b/contrib/sepgsql/expected/label.out
index bac169f..e967c7c 100644
--- a/contrib/sepgsql/expected/label.out
+++ b/contrib/sepgsql/expected/label.out
@@ -26,6 +26,11 @@ 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';
 --
 -- Tests for default labeling behavior
@@ -100,6 +105,223 @@ SELECT sepgsql_getcon();	-- client's label must be restored
 (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)
+
+--
 -- Clean up
 --
 SELECT sepgsql_getcon();	-- confirm client privilege
@@ -115,3 +337,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 340bec6..f5e6267 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,181 @@ 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. It shall be initialized as security
+ * label of the peer process using getpeercon(3); that can be overridden by
+ * the supplied label of sepgsql_setcon() or execution of trusted procedures.
  */
-static char *client_label = NULL;
+static char *client_label_peer		= NULL;	/* set by getpeercon(3) */
+static char *client_label_setcon	= NULL;	/* set by sepgsql_setcon() */
+static List *client_label_pending	= NIL;	/* list of pending_label */
+static char *client_label_func		= NULL;	/* set by trusted procedure */
 
+/*
+ * sepgsql_setcon() enables client to switch current security label as long
+ * as the security policy admit it. It is transaction-aware operations, so
+ * the new label shall be chained to the list of client_label_pending, then
+ * it shall be applied to "client_label_setcon" at transaction-commit time.
+ */
+typedef struct {
+	SubTransactionId	subid;
+	char			   *label;
+} pending_label;
+
+/*
+ * sepgsql_get_client_label
+ *
+ * It returns current security label of the client. Any routins to check
+ * permissions has to use this routine, instead of references to
+ * client_label_* variables above.
+ * It can be overridden with invocation of trusted-procedure, thus, it
+ * returns "client_label_func" if not null. Elsewhere, sepgsql_setcon()
+ * might set an alternative security label using dynamic domain transition.
+ * Neither of them has no special state, the security label being initialized
+ * at database-logon time shall be returned.
+ */
 char *
 sepgsql_get_client_label(void)
 {
-	return client_label;
+	if (client_label_func)
+		return client_label_func;
+	if (client_label_pending)
+	{
+		pending_label  *plabel = llast(client_label_pending);
+		if (plabel->label)
+			return plabel->label;
+	}
+	else if (client_label_setcon)
+		return client_label_setcon;
+	Assert(client_label_peer != NULL);
+	return client_label_peer;
+}
+
+/*
+ * sepgsql_set_client_label
+ *
+ * This routine tried to switch 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;
+
+	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
+	 */
+	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.
+ * It saves an active alternative security label of the client, if exist.
+ */
+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_setcon)
+				pfree(client_label_setcon);
+
+			client_label_setcon = 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.
+ * It shall release an alternative security label set within sub-transaction
+ * being 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 +246,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 +353,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 +369,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;
@@ -231,7 +399,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 +414,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);
 }
 
 /*
@@ -361,6 +533,28 @@ sepgsql_getcon(PG_FUNCTION_ARGS)
 }
 
 /*
+ * BOOL sepgsql_setcon(TEXT)
+ *
+ * It switches the security label of the client, and returns previous
+ * security label.
+ */
+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)
  *
  * It translate the given qualified MLS/MCS range into raw format
diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c
index 8819b8c..0652e29 100644
--- a/contrib/sepgsql/selinux.c
+++ b/contrib/sepgsql/selinux.c
@@ -46,6 +46,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 a8fe247..1d489cd 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
@@ -53,6 +55,15 @@ optional_policy(`
 ')
 
 #
+# 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
 #
 optional_policy(`
@@ -62,11 +73,13 @@ 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};
 	')
 	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;
 ')
 
@@ -76,12 +89,25 @@ optional_policy(`
 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.
+	# sepgsql_regtest_trusted_proc_exec_t.
+	# And, also allows to translate from sepgsql_regtest_dba_t to
+	# sepgsql_regtest_user_t via SET sepgsql.client_label.
 	#
 	gen_require(`
 		attribute sepgsql_client_type;
 	')
 	allow sepgsql_client_type sepgsql_regtest_trusted_proc_exec_t:db_procedure { getattr execute install };
+	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;
+
+	allow sepgsql_regtest_dba_t self : process { setcurrent };
+	allow sepgsql_regtest_dba_t sepgsql_regtest_user_t : process { dyntransition };
+
+	# 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 };
+	type_transition sepgsql_regtest_user_t sepgsql_nosuch_trusted_proc_exec_t:process sepgsql_regtest_nosuch_t;
 ')
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index 9ce8d2d..0eca7aa 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/sql/label.sql b/contrib/sepgsql/sql/label.sql
index 2b18412..24dba45 100644
--- a/contrib/sepgsql/sql/label.sql
+++ b/contrib/sepgsql/sql/label.sql
@@ -31,6 +31,12 @@ 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';
 
 --
@@ -69,6 +75,83 @@ 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();
+
+--
 -- Clean up
 --
 -- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255
@@ -79,3 +162,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;
pgsql-v9.2-sepgsql-setcon.part-1.v2.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-setcon.part-1.v2.patchDownload
 contrib/sepgsql/hooks.c   |  193 +------------------------------------------
 contrib/sepgsql/label.c   |  201 +++++++++++++++++++++++++++++++++++++++++++-
 contrib/sepgsql/sepgsql.h |    2 +-
 3 files changed, 200 insertions(+), 196 deletions(-)

diff --git a/contrib/sepgsql/hooks.c b/contrib/sepgsql/hooks.c
index 47437ba..7093495 100644
--- a/contrib/sepgsql/hooks.c
+++ b/contrib/sepgsql/hooks.c
@@ -18,7 +18,6 @@
 #include "commands/seclabel.h"
 #include "executor/executor.h"
 #include "fmgr.h"
-#include "libpq/auth.h"
 #include "miscadmin.h"
 #include "tcop/utility.h"
 #include "utils/guc.h"
@@ -36,10 +35,7 @@ void		_PG_init(void);
  * Saved hook entries (if stacked)
  */
 static object_access_hook_type next_object_access_hook = NULL;
-static ClientAuthentication_hook_type next_client_auth_hook = NULL;
 static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL;
-static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
-static fmgr_hook_type next_fmgr_hook = NULL;
 static ProcessUtility_hook_type next_ProcessUtility_hook = NULL;
 static ExecutorStart_hook_type next_ExecutorStart_hook = NULL;
 
@@ -82,48 +78,6 @@ sepgsql_get_debug_audit(void)
 }
 
 /*
- * sepgsql_client_auth
- *
- * Entrypoint of the client authentication hook.
- * It switches the client label according to getpeercon(), and the current
- * performing mode according to the GUC setting.
- */
-static void
-sepgsql_client_auth(Port *port, int status)
-{
-	char	   *context;
-
-	if (next_client_auth_hook)
-		(*next_client_auth_hook) (port, status);
-
-	/*
-	 * In the case when authentication failed, the supplied socket shall be
-	 * closed soon, so we don't need to do anything here.
-	 */
-	if (status != STATUS_OK)
-		return;
-
-	/*
-	 * Getting security label of the peer process using API of libselinux.
-	 */
-	if (getpeercon_raw(port->sock, &context) < 0)
-		ereport(FATAL,
-				(errcode(ERRCODE_INTERNAL_ERROR),
-				 errmsg("SELinux: unable to get peer label: %m")));
-
-	sepgsql_set_client_label(context);
-
-	/*
-	 * Switch the current performing mode from INTERNAL to either DEFAULT or
-	 * PERMISSIVE.
-	 */
-	if (sepgsql_permissive)
-		sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE);
-	else
-		sepgsql_set_mode(SEPGSQL_MODE_DEFAULT);
-}
-
-/*
  * sepgsql_object_access
  *
  * Entrypoint of the object_access_hook. This routine performs as
@@ -221,121 +175,6 @@ sepgsql_exec_check_perms(List *rangeTabls, bool abort)
 }
 
 /*
- * sepgsql_needs_fmgr_hook
- *
- * It informs the core whether the supplied function is trusted procedure,
- * or not. If true, sepgsql_fmgr_hook shall be invoked at start, end, and
- * abort time of function invocation.
- */
-static bool
-sepgsql_needs_fmgr_hook(Oid functionId)
-{
-	ObjectAddress	object;
-
-	if (next_needs_fmgr_hook &&
-		(*next_needs_fmgr_hook) (functionId))
-		return true;
-
-	/*
-	 * SELinux needs the function to be called via security_definer wrapper,
-	 * if this invocation will take a domain-transition. We call these
-	 * functions as trusted-procedure, if the security policy has a rule that
-	 * switches security label of the client on execution.
-	 */
-	if (sepgsql_avc_trusted_proc(functionId) != NULL)
-		return true;
-
-	/*
-	 * Even if not a trusted-procedure, this function should not be inlined
-	 * unless the client has db_procedure:{execute} permission. Please note
-	 * that it shall be actually failed later because of same reason with
-	 * ACL_EXECUTE.
-	 */
-	object.classId = ProcedureRelationId;
-	object.objectId = functionId;
-	object.objectSubId = 0;
-	if (!sepgsql_avc_check_perms(&object,
-								 SEPG_CLASS_DB_PROCEDURE,
-								 SEPG_DB_PROCEDURE__EXECUTE,
-								 SEPGSQL_AVC_NOAUDIT, false))
-		return true;
-
-	return false;
-}
-
-/*
- * sepgsql_fmgr_hook
- *
- * It switches security label of the client on execution of trusted
- * procedures.
- */
-static void
-sepgsql_fmgr_hook(FmgrHookEventType event,
-				  FmgrInfo *flinfo, Datum *private)
-{
-	struct
-	{
-		char	   *old_label;
-		char	   *new_label;
-		Datum		next_private;
-	}		   *stack;
-
-	switch (event)
-	{
-		case FHET_START:
-			stack = (void *) DatumGetPointer(*private);
-			if (!stack)
-			{
-				MemoryContext oldcxt;
-
-				oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt);
-				stack = palloc(sizeof(*stack));
-				stack->old_label = NULL;
-				stack->new_label = sepgsql_avc_trusted_proc(flinfo->fn_oid);
-				stack->next_private = 0;
-
-				MemoryContextSwitchTo(oldcxt);
-
-				/*
-				 * process:transition permission between old and new label,
-				 * when user tries to switch security label of the client
-				 * on execution of trusted procedure.
-				 */
-				if (stack->new_label)
-					sepgsql_avc_check_perms_label(stack->new_label,
-												  SEPG_CLASS_PROCESS,
-												  SEPG_PROCESS__TRANSITION,
-												  NULL, true);
-
-				*private = PointerGetDatum(stack);
-			}
-			Assert(!stack->old_label);
-			if (stack->new_label)
-				stack->old_label = sepgsql_set_client_label(stack->new_label);
-
-			if (next_fmgr_hook)
-				(*next_fmgr_hook) (event, flinfo, &stack->next_private);
-			break;
-
-		case FHET_END:
-		case FHET_ABORT:
-			stack = (void *) DatumGetPointer(*private);
-
-			if (next_fmgr_hook)
-				(*next_fmgr_hook) (event, flinfo, &stack->next_private);
-
-			if (stack->old_label)
-				sepgsql_set_client_label(stack->old_label);
-			stack->old_label = NULL;
-			break;
-
-		default:
-			elog(ERROR, "unexpected event type: %d", (int) event);
-			break;
-	}
-}
-
-/*
  * sepgsql_executor_start
  *
  * It saves contextual information during ExecutorStart to distinguish 
@@ -465,8 +304,6 @@ sepgsql_utility_command(Node *parsetree,
 void
 _PG_init(void)
 {
-	char	   *context;
-
 	/*
 	 * We allow to load the SE-PostgreSQL module on single-user-mode or
 	 * shared_preload_libraries settings only.
@@ -522,33 +359,16 @@ _PG_init(void)
 							 NULL,
 							 NULL);
 
-	/*
-	 * Set up dummy client label.
-	 *
-	 * XXX - note that PostgreSQL launches background worker process like
-	 * autovacuum without authentication steps. So, we initialize sepgsql_mode
-	 * with SEPGSQL_MODE_INTERNAL, and client_label with the security context
-	 * of server process. Later, it also launches background of user session.
-	 * 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(&context) < 0)
-		ereport(ERROR,
-				(errcode(ERRCODE_INTERNAL_ERROR),
-				 errmsg("SELinux: failed to get server security label: %m")));
-	sepgsql_set_client_label(context);
-
 	/* Initialize userspace access vector cache */
 	sepgsql_avc_init();
 
+	/* Initialize security label of the client and related stuff */
+	sepgsql_init_client_label();
+
 	/* Security label provider hook */
 	register_label_provider(SEPGSQL_LABEL_TAG,
 							sepgsql_object_relabel);
 
-	/* Client authentication hook */
-	next_client_auth_hook = ClientAuthentication_hook;
-	ClientAuthentication_hook = sepgsql_client_auth;
-
 	/* Object access hook */
 	next_object_access_hook = object_access_hook;
 	object_access_hook = sepgsql_object_access;
@@ -557,13 +377,6 @@ _PG_init(void)
 	next_exec_check_perms_hook = ExecutorCheckPerms_hook;
 	ExecutorCheckPerms_hook = sepgsql_exec_check_perms;
 
-	/* Trusted procedure hooks */
-	next_needs_fmgr_hook = needs_fmgr_hook;
-	needs_fmgr_hook = sepgsql_needs_fmgr_hook;
-
-	next_fmgr_hook = fmgr_hook;
-	fmgr_hook = sepgsql_fmgr_hook;
-
 	/* ProcessUtility hook */
 	next_ProcessUtility_hook = ProcessUtility_hook;
 	ProcessUtility_hook = sepgsql_utility_command;
diff --git a/contrib/sepgsql/label.c b/contrib/sepgsql/label.c
index 2ab7a6f..340bec6 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -22,6 +22,7 @@
 #include "catalog/pg_proc.h"
 #include "commands/dbcommands.h"
 #include "commands/seclabel.h"
+#include "libpq/auth.h"
 #include "libpq/libpq-be.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
@@ -35,6 +36,13 @@
 #include <selinux/label.h>
 
 /*
+ * Saved hook entries (if stacked)
+ */
+static ClientAuthentication_hook_type next_client_auth_hook = NULL;
+static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
+static fmgr_hook_type next_fmgr_hook = NULL;
+
+/*
  * client_label
  *
  * security label of the client process
@@ -47,14 +55,197 @@ sepgsql_get_client_label(void)
 	return client_label;
 }
 
-char *
-sepgsql_set_client_label(char *new_label)
+/*
+ * sepgsql_client_auth
+ *
+ * Entrypoint of the client authentication hook.
+ * It switches the client label according to getpeercon(), and the current
+ * performing mode according to the GUC setting.
+ */
+static void
+sepgsql_client_auth(Port *port, int status)
+{
+	if (next_client_auth_hook)
+		(*next_client_auth_hook) (port, status);
+
+	/*
+	 * In the case when authentication failed, the supplied socket shall be
+	 * closed soon, so we don't need to do anything here.
+	 */
+	if (status != STATUS_OK)
+		return;
+
+	/*
+	 * Getting security label of the peer process using API of libselinux.
+	 */
+	if (getpeercon_raw(port->sock, &client_label) < 0)
+		ereport(FATAL,
+				(errcode(ERRCODE_INTERNAL_ERROR),
+				 errmsg("SELinux: unable to get peer label: %m")));
+
+	/*
+	 * Switch the current performing mode from INTERNAL to either DEFAULT or
+	 * PERMISSIVE.
+	 */
+	if (sepgsql_get_permissive())
+		sepgsql_set_mode(SEPGSQL_MODE_PERMISSIVE);
+	else
+		sepgsql_set_mode(SEPGSQL_MODE_DEFAULT);
+}
+
+/*
+ * sepgsql_needs_fmgr_hook
+ *
+ * It informs the core whether the supplied function is trusted procedure,
+ * or not. If true, sepgsql_fmgr_hook shall be invoked at start, end, and
+ * abort time of function invocation.
+ */
+static bool
+sepgsql_needs_fmgr_hook(Oid functionId)
 {
-	char	   *old_label = client_label;
+	ObjectAddress	object;
+
+	if (next_needs_fmgr_hook &&
+		(*next_needs_fmgr_hook) (functionId))
+		return true;
+
+	/*
+	 * SELinux needs the function to be called via security_definer wrapper,
+	 * if this invocation will take a domain-transition. We call these
+	 * functions as trusted-procedure, if the security policy has a rule that
+	 * switches security label of the client on execution.
+	 */
+	if (sepgsql_avc_trusted_proc(functionId) != NULL)
+		return true;
+
+	/*
+	 * Even if not a trusted-procedure, this function should not be inlined
+	 * unless the client has db_procedure:{execute} permission. Please note
+	 * that it shall be actually failed later because of same reason with
+	 * ACL_EXECUTE.
+	 */
+	object.classId = ProcedureRelationId;
+	object.objectId = functionId;
+	object.objectSubId = 0;
+	if (!sepgsql_avc_check_perms(&object,
+								 SEPG_CLASS_DB_PROCEDURE,
+								 SEPG_DB_PROCEDURE__EXECUTE,
+								 SEPGSQL_AVC_NOAUDIT, false))
+		return true;
+
+	return false;
+}
+
+/*
+ * sepgsql_fmgr_hook
+ *
+ * It switches security label of the client on execution of trusted
+ * procedures.
+ */
+static void
+sepgsql_fmgr_hook(FmgrHookEventType event,
+				  FmgrInfo *flinfo, Datum *private)
+{
+	struct
+	{
+		char	   *old_label;
+		char	   *new_label;
+		Datum		next_private;
+	}		   *stack;
+
+	switch (event)
+	{
+		case FHET_START:
+			stack = (void *) DatumGetPointer(*private);
+			if (!stack)
+			{
+				MemoryContext oldcxt;
+
+				oldcxt = MemoryContextSwitchTo(flinfo->fn_mcxt);
+				stack = palloc(sizeof(*stack));
+				stack->old_label = NULL;
+				stack->new_label = sepgsql_avc_trusted_proc(flinfo->fn_oid);
+				stack->next_private = 0;
+
+				MemoryContextSwitchTo(oldcxt);
+
+				/*
+				 * process:transition permission between old and new label,
+				 * when user tries to switch security label of the client
+				 * on execution of trusted procedure.
+				 */
+				if (stack->new_label)
+					sepgsql_avc_check_perms_label(stack->new_label,
+												  SEPG_CLASS_PROCESS,
+												  SEPG_PROCESS__TRANSITION,
+												  NULL, true);
+
+				*private = PointerGetDatum(stack);
+			}
+			Assert(!stack->old_label);
+			if (stack->new_label)
+			{
+				stack->old_label = client_label;
+				client_label = stack->new_label;
+			}
+			if (next_fmgr_hook)
+				(*next_fmgr_hook) (event, flinfo, &stack->next_private);
+			break;
+
+		case FHET_END:
+		case FHET_ABORT:
+			stack = (void *) DatumGetPointer(*private);
+
+			if (next_fmgr_hook)
+				(*next_fmgr_hook) (event, flinfo, &stack->next_private);
+
+			if (stack->new_label)
+			{
+				client_label = stack->old_label;
+				stack->old_label = NULL;
+			}
+			break;
+
+		default:
+			elog(ERROR, "unexpected event type: %d", (int) event);
+			break;
+	}
+}
+
+/*
+ * sepgsql_init_client_label
+ *
+ * This routine initialize security label of the client, and set up related
+ * hooks to be invoked later.
+ */
+void
+sepgsql_init_client_label(void)
+{
+	/*
+	 * Set up dummy client label.
+	 *
+	 * XXX - note that PostgreSQL launches background worker process like
+	 * autovacuum without authentication steps. So, we initialize sepgsql_mode
+	 * with SEPGSQL_MODE_INTERNAL, and client_label with the security context
+	 * of server process. Later, it also launches background of user session.
+	 * 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)
+		ereport(ERROR,
+				(errcode(ERRCODE_INTERNAL_ERROR),
+				 errmsg("SELinux: failed to get server security label: %m")));
+
+	/* Client authentication hook */
+	next_client_auth_hook = ClientAuthentication_hook;
+	ClientAuthentication_hook = sepgsql_client_auth;
 
-	client_label = new_label;
+	/* Trusted procedure hooks */
+	next_needs_fmgr_hook = needs_fmgr_hook;
+	needs_fmgr_hook = sepgsql_needs_fmgr_hook;
 
-	return old_label;
+	next_fmgr_hook = fmgr_hook;
+	fmgr_hook = sepgsql_fmgr_hook;
 }
 
 /*
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index c93da7a..9ce8d2d 100644
--- a/contrib/sepgsql/sepgsql.h
+++ b/contrib/sepgsql/sepgsql.h
@@ -267,7 +267,7 @@ extern void sepgsql_avc_init(void);
  * label.c
  */
 extern char *sepgsql_get_client_label(void);
-extern char *sepgsql_set_client_label(char *new_label);
+extern void  sepgsql_init_client_label(void);
 extern char *sepgsql_get_label(Oid relOid, Oid objOid, int32 subId);
 
 extern void sepgsql_object_relabel(const ObjectAddress *object,
#18Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#17)
Re: [v9.2] Add GUC sepgsql.client_label

On Sun, Feb 5, 2012 at 4:09 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

The attached part-1 patch moves related routines from hooks.c to
label.c because of references to static variables.

I have committed this part. Seems like a better location for that code, anyhow.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#19Yeb Havinga
yebhavinga@gmail.com
In reply to: Kohei KaiGai (#17)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-02-05 10:09, Kohei KaiGai wrote:

The attached part-1 patch moves related routines from hooks.c to
label.c because of references to static variables. The part-2 patch
implements above mechanism.

I took a short look at this patch but am stuck getting the regression
test to run properly.

First, patch 2 misses the file sepgsql.sql.in and therefore the creation
function command for sepgsql_setcon is missing.

When that was solved, ./test_psql failed on the message that the psql
file may not be of object type unconfined_t. Once it was changed to
bin_t, the result output for the domain transition gives differences on
this output (the other parts of label.sql were ok) :

--
-- Test for Dynamic Domain Transition
--
-- validation of transaction aware dynamic-transition
/usr/bin/runcon: /usr/local/pgsql/bin/psql: Permission denied
/usr/bin/runcon: /usr/local/pgsql/bin/psql: Permission denied
/usr/bin/runcon: /usr/local/pgsql/bin/psql: Permission denied

However when I connect to the regression database directly, I can
execute the first setcon command but get
regression=# SELECT
sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c12');
ERROR: SELinux: security policy violation

logfile shows
LOG: SELinux: denied { dyntransition }
scontext=unconfined_u:unconfined_r:unconfined_t:s0
tcontext=unconfined_u:unconfined_r:unconfined_t:s0:c0.c12 tclass=process

So maybe this is because my start domain is not s0-s0:c0.c1023

However, when trying to run bash or psql in domain
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 I get permission
denied.

Distribution is FC15, sestatus
SELinux status: enabled
SELinuxfs mount: /selinux
Current mode: enforcing
Mode from config file: enforcing
Policy version: 24
Policy from config file: targeted

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#20Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Yeb Havinga (#19)
1 attachment(s)
Re: [v9.2] Add GUC sepgsql.client_label

2012/2/20 Yeb Havinga <yebhavinga@gmail.com>:

On 2012-02-05 10:09, Kohei KaiGai wrote:

The attached part-1 patch moves related routines from hooks.c to label.c
because of references to static variables. The part-2 patch implements above
mechanism.

I took a short look at this patch but am stuck getting the regression test
to run properly.

First, patch 2 misses the file sepgsql.sql.in and therefore the creation
function command for sepgsql_setcon is missing.

Thanks for your comments.

I added the definition of sepgsql_setcon function to sepgsql.sql.in file,
in addition to patch rebasing.

So maybe this is because my start domain is not s0-s0:c0.c1023

However, when trying to run bash or psql in domain
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 I get permission
denied.

Distribution is FC15, sestatus
SELinux status:                 enabled
SELinuxfs mount:                /selinux
Current mode:                   enforcing
Mode from config file:          enforcing
Policy version:                 24
Policy from config file:        targeted

The "default" security policy does not permit dynamic domain transition
even if unconfined domain, in contradiction to its name.
(IMO, it is fair enough design to avoid single point of failure like root user.)

The security policy of regression test contains a set of rules to reduce
categories assigned to unconfined domain.
So, could you try the following steps.
1. Build the latest policy
% make -f /usr/share/selinux/devel/Makefile -C contrib/sepgsql
2. Install the policy module
% sudo semodule -i contrib/sepgsql/sepgsql-regtest.pp
3. Turn on the sepgsql_regression_test_mode
% sudo setsebool -P sepgsql_regression_test_mode=1

I believe it allows to switch security label of the client, as long as we try to
reduce categories.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-sepgsql-setcon.part-2.v3.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-setcon.part-2.v3.patchDownload
 contrib/sepgsql/expected/label.out |  223 ++++++++++++++++++++++++++++++++++++
 contrib/sepgsql/label.c            |  212 ++++++++++++++++++++++++++++++++--
 contrib/sepgsql/selinux.c          |    6 +
 contrib/sepgsql/sepgsql-regtest.te |   36 +++++-
 contrib/sepgsql/sepgsql.h          |    3 +
 contrib/sepgsql/sepgsql.sql.in     |    1 +
 contrib/sepgsql/sql/label.sql      |   84 ++++++++++++++
 7 files changed, 551 insertions(+), 14 deletions(-)

diff --git a/contrib/sepgsql/expected/label.out b/contrib/sepgsql/expected/label.out
index bac169f..e967c7c 100644
--- a/contrib/sepgsql/expected/label.out
+++ b/contrib/sepgsql/expected/label.out
@@ -26,6 +26,11 @@ 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';
 --
 -- Tests for default labeling behavior
@@ -100,6 +105,223 @@ SELECT sepgsql_getcon();	-- client's label must be restored
 (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)
+
+--
 -- Clean up
 --
 SELECT sepgsql_getcon();	-- confirm client privilege
@@ -115,3 +337,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 340bec6..f5e6267 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,181 @@ 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. It shall be initialized as security
+ * label of the peer process using getpeercon(3); that can be overridden by
+ * the supplied label of sepgsql_setcon() or execution of trusted procedures.
  */
-static char *client_label = NULL;
+static char *client_label_peer		= NULL;	/* set by getpeercon(3) */
+static char *client_label_setcon	= NULL;	/* set by sepgsql_setcon() */
+static List *client_label_pending	= NIL;	/* list of pending_label */
+static char *client_label_func		= NULL;	/* set by trusted procedure */
 
+/*
+ * sepgsql_setcon() enables client to switch current security label as long
+ * as the security policy admit it. It is transaction-aware operations, so
+ * the new label shall be chained to the list of client_label_pending, then
+ * it shall be applied to "client_label_setcon" at transaction-commit time.
+ */
+typedef struct {
+	SubTransactionId	subid;
+	char			   *label;
+} pending_label;
+
+/*
+ * sepgsql_get_client_label
+ *
+ * It returns current security label of the client. Any routins to check
+ * permissions has to use this routine, instead of references to
+ * client_label_* variables above.
+ * It can be overridden with invocation of trusted-procedure, thus, it
+ * returns "client_label_func" if not null. Elsewhere, sepgsql_setcon()
+ * might set an alternative security label using dynamic domain transition.
+ * Neither of them has no special state, the security label being initialized
+ * at database-logon time shall be returned.
+ */
 char *
 sepgsql_get_client_label(void)
 {
-	return client_label;
+	if (client_label_func)
+		return client_label_func;
+	if (client_label_pending)
+	{
+		pending_label  *plabel = llast(client_label_pending);
+		if (plabel->label)
+			return plabel->label;
+	}
+	else if (client_label_setcon)
+		return client_label_setcon;
+	Assert(client_label_peer != NULL);
+	return client_label_peer;
+}
+
+/*
+ * sepgsql_set_client_label
+ *
+ * This routine tried to switch 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;
+
+	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
+	 */
+	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.
+ * It saves an active alternative security label of the client, if exist.
+ */
+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_setcon)
+				pfree(client_label_setcon);
+
+			client_label_setcon = 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.
+ * It shall release an alternative security label set within sub-transaction
+ * being 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 +246,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 +353,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 +369,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;
@@ -231,7 +399,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 +414,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);
 }
 
 /*
@@ -361,6 +533,28 @@ sepgsql_getcon(PG_FUNCTION_ARGS)
 }
 
 /*
+ * BOOL sepgsql_setcon(TEXT)
+ *
+ * It switches the security label of the client, and returns previous
+ * security label.
+ */
+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)
  *
  * It translate the given qualified MLS/MCS range into raw format
diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c
index 8819b8c..0652e29 100644
--- a/contrib/sepgsql/selinux.c
+++ b/contrib/sepgsql/selinux.c
@@ -46,6 +46,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 a8fe247..1d489cd 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
@@ -53,6 +55,15 @@ optional_policy(`
 ')
 
 #
+# 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
 #
 optional_policy(`
@@ -62,11 +73,13 @@ 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};
 	')
 	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;
 ')
 
@@ -76,12 +89,25 @@ optional_policy(`
 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.
+	# sepgsql_regtest_trusted_proc_exec_t.
+	# And, also allows to translate from sepgsql_regtest_dba_t to
+	# sepgsql_regtest_user_t via SET sepgsql.client_label.
 	#
 	gen_require(`
 		attribute sepgsql_client_type;
 	')
 	allow sepgsql_client_type sepgsql_regtest_trusted_proc_exec_t:db_procedure { getattr execute install };
+	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;
+
+	allow sepgsql_regtest_dba_t self : process { setcurrent };
+	allow sepgsql_regtest_dba_t sepgsql_regtest_user_t : process { dyntransition };
+
+	# 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 };
+	type_transition sepgsql_regtest_user_t sepgsql_nosuch_trusted_proc_exec_t:process sepgsql_regtest_nosuch_t;
 ')
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index 9ce8d2d..0eca7aa 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 45ffe31..917d12d 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 2b18412..24dba45 100644
--- a/contrib/sepgsql/sql/label.sql
+++ b/contrib/sepgsql/sql/label.sql
@@ -31,6 +31,12 @@ 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';
 
 --
@@ -69,6 +75,83 @@ 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();
+
+--
 -- Clean up
 --
 -- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255
@@ -79,3 +162,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;
#21Yeb Havinga
yebhavinga@gmail.com
In reply to: Kohei KaiGai (#20)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-02-23 12:17, Kohei KaiGai wrote:

2012/2/20 Yeb Havinga<yebhavinga@gmail.com>:

So maybe this is because my start domain is not s0-s0:c0.c1023

However, when trying to run bash or psql in domain
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 I get permission
denied.

Distribution is FC15, sestatus
SELinux status: enabled
SELinuxfs mount: /selinux
Current mode: enforcing
Mode from config file: enforcing
Policy version: 24
Policy from config file: targeted

The "default" security policy does not permit dynamic domain transition
even if unconfined domain, in contradiction to its name.
(IMO, it is fair enough design to avoid single point of failure like root user.)

The security policy of regression test contains a set of rules to reduce
categories assigned to unconfined domain.
So, could you try the following steps.
1. Build the latest policy
% make -f /usr/share/selinux/devel/Makefile -C contrib/sepgsql
2. Install the policy module
% sudo semodule -i contrib/sepgsql/sepgsql-regtest.pp
3. Turn on the sepgsql_regression_test_mode
% sudo setsebool -P sepgsql_regression_test_mode=1

I believe it allows to switch security label of the client, as long as we try to
reduce categories.

I remember these commands from the sepgsql contrib module documentation
(though the semodule invocation in the documentation is with -u and the
setsebool does not have the -P flag). semodule -l showed I had already
installed version 1.04.

I just repeated all steps with the new patch, and get the same result:

LOG: SELinux: denied { dyntransition }
scontext=unconfined_u:unconfined_r:unconfined_t:s0
tcontext=unconfined_u:unconfined_r:unconfined_t:s0:c0.c15 tclass=process
STATEMENT: SELECT
sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c15');

[mgrid@mgfedora sepgsql]$ getsebool sepgsql_regression_test_mode
sepgsql_regression_test_mode --> on
[root@mgfedora sepgsql]# semodule -l | egrep 'pgsql|postgres'
postgresql 1.12.1
sepgsql-regtest 1.04

Do I need Fedora 16 to run it?

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#22Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Yeb Havinga (#21)
Re: [v9.2] Add GUC sepgsql.client_label

2012/2/24 Yeb Havinga <yebhavinga@gmail.com>:

On 2012-02-23 12:17, Kohei KaiGai wrote:

2012/2/20 Yeb Havinga<yebhavinga@gmail.com>:

So maybe this is because my start domain is not s0-s0:c0.c1023

However, when trying to run bash or psql in domain
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 I get permission
denied.

Distribution is FC15, sestatus
SELinux status:                 enabled
SELinuxfs mount:                /selinux
Current mode:                   enforcing
Mode from config file:          enforcing
Policy version:                 24
Policy from config file:        targeted

The "default" security policy does not permit dynamic domain transition
even if unconfined domain, in contradiction to its name.
(IMO, it is fair enough design to avoid single point of failure like root
user.)

The security policy of regression test contains a set of rules to reduce
categories assigned to unconfined domain.
So, could you try the following steps.
1. Build the latest policy
    % make -f /usr/share/selinux/devel/Makefile -C contrib/sepgsql
2. Install the policy module
    % sudo semodule -i contrib/sepgsql/sepgsql-regtest.pp
3. Turn on the sepgsql_regression_test_mode
    % sudo setsebool -P sepgsql_regression_test_mode=1

I believe it allows to switch security label of the client, as long as we
try to
reduce categories.

I remember these commands from the sepgsql contrib module documentation
(though the semodule invocation in the documentation is with -u and the
setsebool does not have the -P flag). semodule -l showed I had already
installed version 1.04.

I just repeated all steps with the new patch, and get the same result:

LOG:  SELinux: denied { dyntransition }
scontext=unconfined_u:unconfined_r:unconfined_t:s0
tcontext=unconfined_u:unconfined_r:unconfined_t:s0:c0.c15 tclass=process
STATEMENT:  SELECT
sepgsql_setcon('unconfined_u:unconfined_r:unconfined_t:s0:c0.c15');

[mgrid@mgfedora sepgsql]$ getsebool sepgsql_regression_test_mode
sepgsql_regression_test_mode --> on
[root@mgfedora sepgsql]# semodule -l | egrep 'pgsql|postgres'
postgresql      1.12.1
sepgsql-regtest 1.04

Do I need Fedora 16 to run it?

Thanks for your continuous testing.

It seems to me you try to expand categories of the client.
The log saids sepgsql_setcon() tries to switch to "...:s0:c0.c15" from "...:s0".
It is not an admitted operations because of increasion of categories.

LOG:  SELinux: denied { dyntransition }
scontext=unconfined_u:unconfined_r:unconfined_t:s0
tcontext=unconfined_u:unconfined_r:unconfined_t:s0:c0.c15 tclass=process

May I see your /etc/selinux/targeted/seusers ?

I think "__default__" entry is configured to "unconfined_u:s0", instead of
"unconfined_u:s0:c0.c1023" as default.

In my environment, it is configured as follows:

[root@iwashi ~]# cat /etc/selinux/targeted/seusers
# This file is auto-generated by libsemanage
# Do not edit directly.

system_u:system_u:s0-s0:c0.c1023
root:unconfined_u:s0-s0:c0.c1023
__default__:unconfined_u:s0-s0:c0.c1023 <=== (*)

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#23Yeb Havinga
yebhavinga@gmail.com
In reply to: Kohei KaiGai (#22)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-02-24 14:20, Kohei KaiGai wrote:

It seems to me you try to expand categories of the client.
The log saids sepgsql_setcon() tries to switch to "...:s0:c0.c15" from "...:s0".
It is not an admitted operations because of increasion of categories.

Yes I had my eye on the missing c0.c1023 before but couldn't remember
changing it, so wrongfully assumed that it would be semantically
equivalent to c0.c1023.

LOG: SELinux: denied { dyntransition }
scontext=unconfined_u:unconfined_r:unconfined_t:s0
tcontext=unconfined_u:unconfined_r:unconfined_t:s0:c0.c15 tclass=process

May I see your /etc/selinux/targeted/seusers ?

I think "__default__" entry is configured to "unconfined_u:s0", instead of
"unconfined_u:s0:c0.c1023" as default.

In my environment, it is configured as follows:

[root@iwashi ~]# cat /etc/selinux/targeted/seusers
# This file is auto-generated by libsemanage
# Do not edit directly.

system_u:system_u:s0-s0:c0.c1023
root:unconfined_u:s0-s0:c0.c1023
__default__:unconfined_u:s0-s0:c0.c1023<=== (*)

[mgrid@mgfedora ~]$ cat /etc/selinux/targeted/seusers
# This file is auto-generated by libsemanage
# Do not edit directly.

system_u:system_u:s0-s0:c0.c1023
root:unconfined_u:s0-s0:c0.c1023
__default__:unconfined_u:s0-s0:c0.c1023

but still

[mgrid@mgfedora ~]$ id -Z
system_u:unconfined_r:unconfined_t:s0
(I changed bash to run in the unconfined_u context before starting the
regression test)

and

[root@mgfedora targeted]# id -Z
system_u:unconfined_r:unconfined_t:s0

When I created a new test user, it's selinux context showed the c0.c1023
- I don't know what's fishy about the mgrid user and root that causes
c0.c1023 to be absent. Maybe I should reinstall this virtual machine.
After setting the user "mgrid" on s0-s0:c0.c1023 with

semanage login -a -S targeted -s "unconfined_u" -r s0-s0:c0.c1023 mgrid

the regression tests pass :-)

test label ... ok
test dml ... ok
test create ... ok
test misc ... ok

I'll continue reviewing the patch.

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#24Yeb Havinga
yebhavinga@gmail.com
In reply to: Yeb Havinga (#23)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-02-24 15:17, Yeb Havinga wrote:

I don't know what's fishy about the mgrid user and root that causes
c0.c1023 to be absent.

more info:

In shells started in a x environment under Xvnc, id -Z shows the
system_u and c0.c1023 absent.

In shells started from the ssh daemon, the id -Z matches what it should
be according to the seusers file: unconfined_u and c0.c1023 present.

-- Yeb

#25Yeb Havinga
yebhavinga@gmail.com
In reply to: Kohei KaiGai (#20)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-02-23 12:17, Kohei KaiGai wrote:

2012/2/20 Yeb Havinga<yebhavinga@gmail.com>:

On 2012-02-05 10:09, Kohei KaiGai wrote:

The attached part-1 patch moves related routines from hooks.c to label.c
because of references to static variables. The part-2 patch implements above
mechanism.

I took a short look at this patch but am stuck getting the regression test
to run properly.

First, patch 2 misses the file sepgsql.sql.in and therefore the creation
function command for sepgsql_setcon is missing.

Thanks for your comments.

I added the definition of sepgsql_setcon function to sepgsql.sql.in file,
in addition to patch rebasing.

Very brief comments due to must leave keyboard soon:

I read the source code and played a bit with setcon and the debugger, no
strange things found.

Code comments / questions:

this comment below is a lie, because setcon is set by
sepgsql_xact_callback()
maybe client_label_committed is a better name for client_label_setcon?

static char *client_label_setcon = NULL; /* set by sepgsql_setcon() */

Is the double negation in the sentence below intended?

+ * Neither of them has no special state, the security label being 
initialized
+ * at database-logon time shall be returned.

Is the assert client_label_peer != NULL in sepgsql_get_client_label
necessary?

sepgsql_set_client_label(), maybe add a comment to !new_label that it is
reset to the peer label.

new_label == NULL / pending_label->label == NULL means use the peer
label. Why not use the peer label instead?

set_label: if new_label == current label according to getcon, is it
necessary to add to the pending list?

sepgsql_subxact_callback(), could this be made easier to read by just
taking llast(client_label_pending), assert that plabel->subid == mySubId
and then list_delete on pointer of that listcell?

Some comments contain typos, I can spend some time on this, though I'm
not a native english speaker so it won't be perfect.

regards,
Yeb Havinga

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#26Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Yeb Havinga (#24)
Re: [v9.2] Add GUC sepgsql.client_label

2012/2/24 Yeb Havinga <yebhavinga@gmail.com>:

On 2012-02-24 15:17, Yeb Havinga wrote:

I don't know what's fishy about the mgrid user and root that causes
c0.c1023 to be absent.

more info:

In shells started in a x environment under Xvnc, id -Z shows the system_u
and c0.c1023 absent.

In shells started from the ssh daemon, the id -Z matches what it should be
according to the seusers file: unconfined_u and c0.c1023 present.

It seems to me the reason why your security label on bash is different from
the expected default one.
Unlike sshd daemon, vncserver does not assign security label on itself
according to the /etc/selinux/targeted/seusers, thus it inherits the label
of system startup script. It is also the reason why you saw "system_u"
at the head of security context.

I'll report this topic to selinux community to discuss the preferable solution.
Anyway, please use ssh connection for the testing purpose.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#27Yeb Havinga
yebhavinga@gmail.com
In reply to: Yeb Havinga (#25)
1 attachment(s)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-02-24 17:25, Yeb Havinga wrote:

On 2012-02-23 12:17, Kohei KaiGai wrote:

2012/2/20 Yeb Havinga<yebhavinga@gmail.com>:

On 2012-02-05 10:09, Kohei KaiGai wrote:

The attached part-1 patch moves related routines from hooks.c to
label.c
because of references to static variables. The part-2 patch
implements above
mechanism.

I took a short look at this patch but am stuck getting the
regression test
to run properly.

First, patch 2 misses the file sepgsql.sql.in and therefore the
creation
function command for sepgsql_setcon is missing.

Thanks for your comments.

I added the definition of sepgsql_setcon function to sepgsql.sql.in
file,
in addition to patch rebasing.

Very brief comments due to must leave keyboard soon:

I read the source code and played a bit with setcon and the debugger,
no strange things found.

Code comments / questions:

I took the liberty to change a few things, mostly comments, in the
attached patch:

maybe client_label_committed is a better name for client_label_setcon?

this change was made.

Is the double negation in the sentence below intended?

several comments were changed / moved. There is now one place where te
behaviour of the different client_label variables are explained.

sepgsql_set_client_label(), maybe add a comment to !new_label that it
is reset to the peer label.

done.

Is the assert client_label_peer != NULL in sepgsql_get_client_label
necessary?
new_label == NULL / pending_label->label == NULL means use the peer
label. Why not use the peer label instead?

It turned out that pending_label->label is invariantly non null. Changed
code to assume that and added some Asserts.

set_label: if new_label == current label according to getcon, is it
necessary to add to the pending list?

this question still remains. Maybe there would be reasons to hit selinux
with the question: can I change from A->A.

sepgsql_subxact_callback(), could this be made easier to read by just
taking llast(client_label_pending), assert that plabel->subid ==
mySubId and then list_delete on pointer of that listcell?

no this was a naieve suggestion, which fails in any case of a
subtransaction with not exactly one call to sepgsql_setcon()

Some comments contain typos, I can spend some time on this, though I'm
not a native english speaker so it won't be perfect.

sgml documentation must still be added. If time permits I can spend some
time on that tomorrow.

regards,
Yeb Havinga

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

Attachments:

pgsql-v9.2-sepgsql-setcon.part2-v3.patchtext/x-patch; name=pgsql-v9.2-sepgsql-setcon.part2-v3.patchDownload
diff --git a/contrib/sepgsql/expected/label.out b/contrib/sepgsql/expected/label.out
index bac169f..e967c7c 100644
--- a/contrib/sepgsql/expected/label.out
+++ b/contrib/sepgsql/expected/label.out
@@ -26,6 +26,11 @@ 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';
 --
 -- Tests for default labeling behavior
@@ -100,6 +105,223 @@ SELECT sepgsql_getcon();	-- client's label must be restored
 (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)
+
+--
 -- Clean up
 --
 SELECT sepgsql_getcon();	-- confirm client privilege
@@ -115,3 +337,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 340bec6..7919008 100644
--- a/contrib/sepgsql/label.c
+++ b/contrib/sepgsql/label.c
@@ -1,6 +1,6 @@
 /* -------------------------------------------------------------------------
  *
- * contrib/sepgsql/label.c
+ * contrib/sepgsqlet/label.c
  *
  * Routines to support SELinux labels (security context)
  *
@@ -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,178 @@ 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;	/* list addition by sepgsql_setcon() */
+static char *client_label_committed	= NULL;	/* set by sepgsql_xact_callback() */
+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);
+
+		Assert(plabel->label != NULL);
+		return plabel->label;
+	}
+
+	/* committed sepgsql_setcon() value */
+	else if (client_label_committed)
+		return client_label_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;
+
+	if (!new_label)
+		/* Reset to the initial client 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. */
+	oldcxt = MemoryContextSwitchTo(CurTransactionContext);
+
+	plabel = palloc0(sizeof(pending_label));
+	plabel->subid = GetCurrentSubTransactionId();
+
+	Assert(new_label != NULL);
+	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;
+
+			Assert(plabel->label != NULL);
+			new_label = MemoryContextStrdup(TopMemoryContext,
+											plabel->label);
+
+			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 +243,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 +350,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 +366,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 +380,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 +396,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 +411,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);
 }
 
 /*
@@ -361,6 +530,27 @@ sepgsql_getcon(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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)
  *
  * It translate the given qualified MLS/MCS range into raw format
diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c
index 8819b8c..0652e29 100644
--- a/contrib/sepgsql/selinux.c
+++ b/contrib/sepgsql/selinux.c
@@ -46,6 +46,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 a8fe247..1d489cd 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
@@ -53,6 +55,15 @@ optional_policy(`
 ')
 
 #
+# 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
 #
 optional_policy(`
@@ -62,11 +73,13 @@ 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};
 	')
 	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;
 ')
 
@@ -76,12 +89,25 @@ optional_policy(`
 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.
+	# sepgsql_regtest_trusted_proc_exec_t.
+	# And, also allows to translate from sepgsql_regtest_dba_t to
+	# sepgsql_regtest_user_t via SET sepgsql.client_label.
 	#
 	gen_require(`
 		attribute sepgsql_client_type;
 	')
 	allow sepgsql_client_type sepgsql_regtest_trusted_proc_exec_t:db_procedure { getattr execute install };
+	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;
+
+	allow sepgsql_regtest_dba_t self : process { setcurrent };
+	allow sepgsql_regtest_dba_t sepgsql_regtest_user_t : process { dyntransition };
+
+	# 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 };
+	type_transition sepgsql_regtest_user_t sepgsql_nosuch_trusted_proc_exec_t:process sepgsql_regtest_nosuch_t;
 ')
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index 9ce8d2d..0eca7aa 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 45ffe31..917d12d 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 2b18412..24dba45 100644
--- a/contrib/sepgsql/sql/label.sql
+++ b/contrib/sepgsql/sql/label.sql
@@ -31,6 +31,12 @@ 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';
 
 --
@@ -69,6 +75,83 @@ 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();
+
+--
 -- Clean up
 --
 -- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255
@@ -79,3 +162,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;
#28Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Yeb Havinga (#27)
1 attachment(s)
Re: [v9.2] Add GUC sepgsql.client_label

Hi, Yeb.

Thanks for your reviewing and patch updates.
(and sorry my delayed response...)

I'd like to point out a case when plabel->label is NULL.

In case of sepgsql_setcon() being invoked with null argument
to reset security label of the client, but not committed yet,
the last item of the client_label_pending has null label.
(It performs as a mark of a security label being reset.)

It is a case when sepgsql_get_client_label() should return
the client_label_peer, not plabel->label.
So, I reverted some of your replacement; that assumes the
pending label is valid with Assert() check to null value.

Most of comments update are quite helpful for me.
So, I merged your revised one in this patch.

Thanks so much!

2012/3/3 Yeb Havinga <yebhavinga@gmail.com>:

On 2012-02-24 17:25, Yeb Havinga wrote:

On 2012-02-23 12:17, Kohei KaiGai wrote:

2012/2/20 Yeb Havinga<yebhavinga@gmail.com>:

On 2012-02-05 10:09, Kohei KaiGai wrote:

The attached part-1 patch moves related routines from hooks.c to
label.c
because of references to static variables. The part-2 patch implements
above
mechanism.

I took a short look at this patch but am stuck getting the regression
test
to run properly.

First, patch 2 misses the file sepgsql.sql.in and therefore the creation
function command for sepgsql_setcon is missing.

Thanks for your comments.

I added the definition of sepgsql_setcon function to sepgsql.sql.in file,
in addition to patch rebasing.

Very brief comments due to must leave keyboard soon:

I read the source code and played a bit with setcon and the debugger, no
strange things found.

Code comments / questions:

I took the liberty to change a few things, mostly comments, in the attached
patch:

maybe client_label_committed is a better name for client_label_setcon?

this change was made.

Is the double negation in the sentence below intended?

several comments were changed / moved. There is now one place where te
behaviour of the different client_label variables are explained.

sepgsql_set_client_label(), maybe add a comment to !new_label that it is
reset to the peer label.

done.

Is the assert client_label_peer != NULL in sepgsql_get_client_label
necessary?
new_label == NULL / pending_label->label == NULL means use the peer label.
Why not use the peer label instead?

It turned out that pending_label->label is invariantly non null. Changed
code to assume that and added some Asserts.

set_label: if new_label == current label according to getcon, is it
necessary to add to the pending list?

this question still remains. Maybe there would be reasons to hit selinux
with the question: can I change from A->A.

sepgsql_subxact_callback(), could this be made easier to read by just
taking llast(client_label_pending), assert that plabel->subid == mySubId and
then list_delete on pointer of that listcell?

no this was a naieve suggestion, which fails in any case of a subtransaction
with not exactly one call to sepgsql_setcon()

Some comments contain typos, I can spend some time on this, though I'm not
a native english speaker so it won't be perfect.

sgml documentation must still be added. If time permits I can spend some
time on that tomorrow.

regards,
Yeb Havinga

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-sepgsql-setcon.part-2.v4.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-setcon.part-2.v4.patchDownload
 contrib/sepgsql/expected/label.out |  223 ++++++++++++++++++++++++++++++++++++
 contrib/sepgsql/label.c            |  216 +++++++++++++++++++++++++++++++++--
 contrib/sepgsql/selinux.c          |    6 +
 contrib/sepgsql/sepgsql-regtest.te |   36 +++++-
 contrib/sepgsql/sepgsql.h          |    3 +
 contrib/sepgsql/sepgsql.sql.in     |    1 +
 contrib/sepgsql/sql/label.sql      |   84 ++++++++++++++
 7 files changed, 553 insertions(+), 16 deletions(-)

diff --git a/contrib/sepgsql/expected/label.out b/contrib/sepgsql/expected/label.out
index bac169f..e967c7c 100644
--- a/contrib/sepgsql/expected/label.out
+++ b/contrib/sepgsql/expected/label.out
@@ -26,6 +26,11 @@ 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';
 --
 -- Tests for default labeling behavior
@@ -100,6 +105,223 @@ SELECT sepgsql_getcon();	-- client's label must be restored
 (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)
+
+--
 -- Clean up
 --
 SELECT sepgsql_getcon();	-- confirm client privilege
@@ -115,3 +337,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 340bec6..deadd88 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);
 }
 
 /*
@@ -361,6 +534,27 @@ sepgsql_getcon(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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)
  *
  * It translate the given qualified MLS/MCS range into raw format
diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c
index 8819b8c..0652e29 100644
--- a/contrib/sepgsql/selinux.c
+++ b/contrib/sepgsql/selinux.c
@@ -46,6 +46,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 a8fe247..1d489cd 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
@@ -53,6 +55,15 @@ optional_policy(`
 ')
 
 #
+# 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
 #
 optional_policy(`
@@ -62,11 +73,13 @@ 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};
 	')
 	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;
 ')
 
@@ -76,12 +89,25 @@ optional_policy(`
 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.
+	# sepgsql_regtest_trusted_proc_exec_t.
+	# And, also allows to translate from sepgsql_regtest_dba_t to
+	# sepgsql_regtest_user_t via SET sepgsql.client_label.
 	#
 	gen_require(`
 		attribute sepgsql_client_type;
 	')
 	allow sepgsql_client_type sepgsql_regtest_trusted_proc_exec_t:db_procedure { getattr execute install };
+	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;
+
+	allow sepgsql_regtest_dba_t self : process { setcurrent };
+	allow sepgsql_regtest_dba_t sepgsql_regtest_user_t : process { dyntransition };
+
+	# 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 };
+	type_transition sepgsql_regtest_user_t sepgsql_nosuch_trusted_proc_exec_t:process sepgsql_regtest_nosuch_t;
 ')
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index 9ce8d2d..0eca7aa 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 45ffe31..917d12d 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 2b18412..24dba45 100644
--- a/contrib/sepgsql/sql/label.sql
+++ b/contrib/sepgsql/sql/label.sql
@@ -31,6 +31,12 @@ 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';
 
 --
@@ -69,6 +75,83 @@ 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();
+
+--
 -- Clean up
 --
 -- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255
@@ -79,3 +162,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;
#29Yeb Havinga
yebhavinga@gmail.com
In reply to: Kohei KaiGai (#28)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-03-06 15:14, Kohei KaiGai wrote:

In case of sepgsql_setcon() being invoked with null argument
to reset security label of the client, but not committed yet,
the last item of the client_label_pending has null label.
(It performs as a mark of a security label being reset.)

Yes, I see that now. Another solution could be to append
client_label_peer on the pending list instead of NULL, but maybe this is
not important enough to discuss. I tried to do some testing by first
transition into a smaller MLS context, then reset to the original again,
but that is not allowed by the regtest policy. I searched the internet
for 30 minutes about how to write a allow rule that would allow e.g. the
transition from c1.c4 back to c1.c1023 but failed.

two other minor nitpicks

-- * contrib/sepgsql/label.c
-+ * contrib/sepgsqlet/label.c

typo here..

-+      /* Append the supplied new_label on the pending list. */
++      /*
++       * Append the supplied new_label on the pending list until
++       * the current transaction is committed.
++       */

the 'until the current transaction is committed' part is something going
on outside of sepgsql_set_client_label() - this function just appends to
the list, always. That the list is reset on transaction commit/abort
time, is done and also already documented elsewhere. A new reader could
be confused to not find transaction related things in
sepgsql_set_client_label().

regards,
Yeb

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#30Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#28)
Re: [v9.2] Add GUC sepgsql.client_label

On Tue, Mar 6, 2012 at 9:14 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

[ new patch ]

Are we absolutely certain that we want the semantics of
sepgsql_setcon() to be transactional? Because if we made them
non-transactional, this would be a whole lot simpler, and it would
still meet the originally proposed use case, which is to allow the
security context of a connection to be changed on a one-time basis
before handing it off to a client application.

As a separate but related note, the label management here seems to be
excessively complicated. In particular, it seems to me that the
semantics of sepgsql_get_client_label() become quite muddled under
this patch. An explicitly-set label overrides the default label, but
a trusted procedure's temporary label overrides that. So if you enter
a trusted procedure, and it calls sepgsql_setcon(), it'll change the
label, but no actual security transition will occur; then, when you
exit the trusted procedure, you'll get the new label in place of
whatever the caller had before. That seems like one heck of a
surprising set of semantics.

It seems to me that it would make more sense to view the set of
security labels in effect as a stack. When we enter a trusted
procedure, it pushes a new label on the stack; when we exit a trusted
procedure, it pops the top label off the stack. sepgsql_setcon()
changes the top label on the stack. If we want to retain
transactional semantics, then you can also have a toplevel label that
doesn't participate in the stack; a commit copies the sole item on the
stack into the toplevel label, and transaction start copies the
toplevel label into an empty stack. In the current coding, I think
client_label_peer is redundant with client_label_committed - once the
latter is set, the former is unused, IIUC - and what I'm proposing is
that client_label_func shouldn't be separate, but rather should mutate
the stack of labels maintained by client_label_pending.

The docs need updating, both to reflect the version bump in
sepgsql-regtest.te and to describe the actual feature.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#31Yeb Havinga
yebhavinga@gmail.com
In reply to: Robert Haas (#30)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-03-09 21:49, Robert Haas wrote:

On Tue, Mar 6, 2012 at 9:14 AM, Kohei KaiGai<kaigai@kaigai.gr.jp> wrote:

[ new patch ]

Are we absolutely certain that we want the semantics of
sepgsql_setcon() to be transactional? Because if we made them
non-transactional, this would be a whole lot simpler, and it would
still meet the originally proposed use case, which is to allow the
security context of a connection to be changed on a one-time basis
before handing it off to a client application.

It would meet the original use case, but outside of that use case it
would be very easy to get POLA violations. Imagine a transaction like
1- do stuff under label A
2- setcon to B
3- do stuff under label B

When that transaction fails due to a serialization error, one would
expect that when the transaction is replayed, the initial actions are
executed under label A. If it was B, or any other further label in the
original transaction, it would be very hard to develop software in user
space that could cope with this behaviour.

As a separate but related note, the label management here seems to be
excessively complicated. In particular, it seems to me that the
semantics of sepgsql_get_client_label() become quite muddled under
this patch. An explicitly-set label overrides the default label, but
a trusted procedure's temporary label overrides that. So if you enter
a trusted procedure, and it calls sepgsql_setcon(), it'll change the
label, but no actual security transition will occur; then, when you
exit the trusted procedure, you'll get the new label in place of
whatever the caller had before. That seems like one heck of a
surprising set of semantics.

I agree that sepgsql_get_*client*_label does not really match with a
trusted procedure temporarily changing it.

It seems to me that it would make more sense to view the set of
security labels in effect as a stack. When we enter a trusted
procedure, it pushes a new label on the stack; when we exit a trusted
procedure, it pops the top label off the stack. sepgsql_setcon()
changes the top label on the stack. If we want to retain
transactional semantics, then you can also have a toplevel label that
doesn't participate in the stack; a commit copies the sole item on the
stack into the toplevel label, and transaction start copies the
toplevel label into an empty stack.

Yes the additions be sepgsql_setcon look like a stack for pushing.
However, the current code that rollbacks the pending list to a certain
savepoint matches code from e.g. StandbyReleaseLocks(), so having a
concept like this as a list is not unknown to the current codebase.

In the current coding, I think
client_label_peer is redundant with client_label_committed - once the
latter is set, the former is unused, IIUC

client_label_peer is used on reset of the client label, i.e. calling
sepgsql_setcon with NULL.

- and what I'm proposing is
that client_label_func shouldn't be separate, but rather should mutate
the stack of labels maintained by client_label_pending.

The drawback of having trusted procedures push things on this stack is
that it would add a memory alloc and size overhead when calling trusted
functions, especially for recursive functions.

The docs need updating, both to reflect the version bump in
sepgsql-regtest.te and to describe the actual feature.

I can probably write some docs tomorrow.

regards,
Yeb Havinga

#32Robert Haas
robertmhaas@gmail.com
In reply to: Yeb Havinga (#31)
Re: [v9.2] Add GUC sepgsql.client_label

On Sat, Mar 10, 2012 at 4:39 AM, Yeb Havinga <yebhavinga@gmail.com> wrote:

As a separate but related note, the label management here seems to be
excessively complicated.  In particular, it seems to me that the
semantics of sepgsql_get_client_label() become quite muddled under
this patch.  An explicitly-set label overrides the default label, but
a trusted procedure's temporary label overrides that.  So if you enter
a trusted procedure, and it calls sepgsql_setcon(), it'll change the
label, but no actual security transition will occur; then, when you
exit the trusted procedure, you'll get the new label in place of
whatever the caller had before.  That seems like one heck of a
surprising set of semantics.

I agree that sepgsql_get_*client*_label does not really match with a trusted
procedure temporarily changing it.

I'm not complaining about the name of the function; I'm complaining
about the semantics.

  In the current coding, I think
client_label_peer is redundant with client_label_committed - once the
latter is set, the former is unused, IIUC

client_label_peer is used on reset of the client label, i.e. calling
sepgsql_setcon with NULL.

Ugh. What's the point of supporting that?

The drawback of having trusted procedures push things on this stack is that
it would add a memory alloc and size overhead when calling trusted
functions, especially for recursive functions.

Compared to all the other overhead of using sepgsql, that is miniscule
and not worth worrying about.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#33Yeb Havinga
yebhavinga@gmail.com
In reply to: Robert Haas (#32)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-03-10 14:06, Robert Haas wrote:

On Sat, Mar 10, 2012 at 4:39 AM, Yeb Havinga<yebhavinga@gmail.com> wrote:

As a separate but related note, the label management here seems to be
excessively complicated. In particular, it seems to me that the
semantics of sepgsql_get_client_label() become quite muddled under
this patch. An explicitly-set label overrides the default label, but
a trusted procedure's temporary label overrides that. So if you enter
a trusted procedure, and it calls sepgsql_setcon(), it'll change the
label, but no actual security transition will occur; then, when you
exit the trusted procedure, you'll get the new label in place of
whatever the caller had before. That seems like one heck of a
surprising set of semantics.

I agree that sepgsql_get_*client*_label does not really match with a trusted
procedure temporarily changing it.

I'm not complaining about the name of the function; I'm complaining
about the semantics.

The semantics are muddled because the client labels are mixed with
labels from trusted procedures. The stack you described upthread may
also exhibit surprising behaviour. If a trusted procedure is called,
it's label is pushed onto the stack. Suppose it pushes another label by
calling sepgsql_setcon(). In the stack case, this is now the top of the
stack and the result of sepgsql_get_client_label(). The procedure
exists. What should now be the result of sepgsql_get_client_label()?
Since labels are managed by a stack, we can only pop what's on top and
need to pop twice, so we've lost the label pushed onto the stack by the
trusted function, which means that trusted procedures cannot be used to
change client labels beyond their own scope, but other functions would.

Maybe this semantics muddling of trusted procedures and setting the
client label can be avoided by denying changing the client label with
sepgsql_setcon() from a trusted procedure, which would also make some sense:

ERROR: cannot override the client label of a trusted procedure during
execution.

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#34Yeb Havinga
yebhavinga@gmail.com
In reply to: Yeb Havinga (#31)
1 attachment(s)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-03-10 10:39, I wrote:

I can probably write some docs tomorrow.

Attached is v5 of the patch, with is exactly equal to v4 but with added
documentation.

Some other notes.

- Robert asked why sepgsql_setcon with NULL to reset the value to the
initial client label was supported. Maybe this could be a leftover from
the initial implementation as GUC variable?
- earlier I suggested preventing setting a new client label from a
trusted procedure, however I just read in the original post that this
was how the current usecase of Joshua is set up. Suggestion withdrawn.

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

Attachments:

pgsql-v9.2-sepgsql-setcon.part2-v5.patchtext/x-patch; name=pgsql-v9.2-sepgsql-setcon.part2-v5.patchDownload
diff --git a/contrib/sepgsql/expected/label.out b/contrib/sepgsql/expected/label.out
index bac169f..e967c7c 100644
--- a/contrib/sepgsql/expected/label.out
+++ b/contrib/sepgsql/expected/label.out
@@ -26,6 +26,11 @@ 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';
 --
 -- Tests for default labeling behavior
@@ -100,6 +105,223 @@ SELECT sepgsql_getcon();	-- client's label must be restored
 (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)
+
+--
 -- Clean up
 --
 SELECT sepgsql_getcon();	-- confirm client privilege
@@ -115,3 +337,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 340bec6..deadd88 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);
 }
 
 /*
@@ -361,6 +534,27 @@ sepgsql_getcon(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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)
  *
  * It translate the given qualified MLS/MCS range into raw format
diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c
index 8819b8c..0652e29 100644
--- a/contrib/sepgsql/selinux.c
+++ b/contrib/sepgsql/selinux.c
@@ -46,6 +46,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 a8fe247..1d489cd 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
@@ -53,6 +55,15 @@ optional_policy(`
 ')
 
 #
+# 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
 #
 optional_policy(`
@@ -62,11 +73,13 @@ 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};
 	')
 	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;
 ')
 
@@ -76,12 +89,25 @@ optional_policy(`
 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.
+	# sepgsql_regtest_trusted_proc_exec_t.
+	# And, also allows to translate from sepgsql_regtest_dba_t to
+	# sepgsql_regtest_user_t via SET sepgsql.client_label.
 	#
 	gen_require(`
 		attribute sepgsql_client_type;
 	')
 	allow sepgsql_client_type sepgsql_regtest_trusted_proc_exec_t:db_procedure { getattr execute install };
+	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;
+
+	allow sepgsql_regtest_dba_t self : process { setcurrent };
+	allow sepgsql_regtest_dba_t sepgsql_regtest_user_t : process { dyntransition };
+
+	# 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 };
+	type_transition sepgsql_regtest_user_t sepgsql_nosuch_trusted_proc_exec_t:process sepgsql_regtest_nosuch_t;
 ')
diff --git a/contrib/sepgsql/sepgsql.h b/contrib/sepgsql/sepgsql.h
index 9ce8d2d..0eca7aa 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 45ffe31..917d12d 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 2b18412..24dba45 100644
--- a/contrib/sepgsql/sql/label.sql
+++ b/contrib/sepgsql/sql/label.sql
@@ -31,6 +31,12 @@ 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';
 
 --
@@ -69,6 +75,83 @@ 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();
+
+--
 -- Clean up
 --
 -- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255
@@ -79,3 +162,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 68cc607..d6807cf 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
 </screen>
 
   <para>
@@ -512,6 +512,47 @@ postgres=# SELECT cid, cname, show_credit(cid) FROM customer;
   </sect3>
 
   <sect3>
+    <title>Dynamic domain transitions</title>
+    <para>
+      It is possible to use SELinux's dynamic domain transition feature to switch
+      the security context 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 are considered dangerous, because they violate
+      a SELinux concept called label tranquility, which means that object and
+      process labels do not change in a running system, after they are
+      initially configured. 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>
+  </sect3>
+
+  <sect3>
    <title>Miscellaneous</title>
    <para>
     We reject the <xref linkend="sql-load"> command across the board, because
@@ -519,6 +560,51 @@ 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 to the new domain, if allowed by the security policy.
+      </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.
+       <literal>cube(1) == '(1)'</literal>
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
  </sect2>
 
  <sect2 id="sepgsql-limitations">
#35Yeb Havinga
yebhavinga@gmail.com
In reply to: Yeb Havinga (#34)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-03-11 11:33, Yeb Havinga wrote:

On 2012-03-10 10:39, I wrote:

I can probably write some docs tomorrow.

Attached is v5 of the patch, with is exactly equal to v4 but with
added documentation.

s/<literal>cube(1) == '(1)'</literal>// in that patch please

#36Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#30)
Re: [v9.2] Add GUC sepgsql.client_label

2012/3/9 Robert Haas <robertmhaas@gmail.com>:

On Tue, Mar 6, 2012 at 9:14 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

[ new patch ]

Are we absolutely certain that we want the semantics of
sepgsql_setcon() to be transactional?  Because if we made them
non-transactional, this would be a whole lot simpler, and it would
still meet the originally proposed use case, which is to allow the
security context of a connection to be changed on a one-time basis
before handing it off to a client application.

I hesitate to implement sepgsql_setcon() being not-transaction aware,
because any other functionality are all transaction aware, such as
SECURITY LABEL statement.
Even though the original use-case from Joshua does not require it
being transaction-aware, I'd like to keep consistency of behavior
with any other features. Is it really unacceptable complexity?

It seems to me that it would make more sense to view the set of
security labels in effect as a stack.  When we enter a trusted
procedure, it pushes a new label on the stack; when we exit a trusted
procedure, it pops the top label off the stack.  sepgsql_setcon()
changes the top label on the stack.  If we want to retain
transactional semantics, then you can also have a toplevel label that
doesn't participate in the stack; a commit copies the sole item on the
stack into the toplevel label, and transaction start copies the
toplevel label into an empty stack.  In the current coding, I think
client_label_peer is redundant with client_label_committed - once the
latter is set, the former is unused, IIUC - and what I'm proposing is
that client_label_func shouldn't be separate, but rather should mutate
the stack of labels maintained by client_label_pending.

I almost agree with your opinion. A semantics to stack security label
will be more straight-forward.

One reason of the redundant client_label_peer is to support reset
client label using NULL-input on sepgsql_setcon(). We need to save
the original value somewhere.

In case of sepgsql_setcon() being invoked inside of the trusted-
procedure, indeed, the existing behavior is confusing as you pointed
out. If we would changed the semantics using a stack structure,
an invocation of trusted procedure takes a label transition from A to B,
then it invokes sepgsql_setcon() to take a label transition from B to C,
and the label "B" shall be popped from the stack at end of the trusted
procedure. Eventually, the label "C" shall be applied on top of the stack.
Do we share same image to whole of the idea?

The docs need updating, both to reflect the version bump in
sepgsql-regtest.te and to describe the actual feature.

OK, please wait for a few days.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#37Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Yeb Havinga (#34)
Re: [v9.2] Add GUC sepgsql.client_label

2012/3/11 Yeb Havinga <yebhavinga@gmail.com>:

On 2012-03-10 10:39, I  wrote:

I can probably write some docs tomorrow.

Attached is v5 of the patch, with is exactly equal to v4 but with added
documentation.

Thanks for your dedicated volunteer. I'm under checking of the updates
at documentation.

Some other notes.

- Robert asked why sepgsql_setcon with NULL to reset the value to the
initial client label was supported. Maybe this could be a leftover from the
initial implementation as GUC variable?

It is a practical reason. In case when httpd open the connection to PG and
set a suitable security label according to the given credential prior to launch
of user application, then keep this connection for upcoming request, it is
worthwhile to reset security label of the client.

- earlier I suggested preventing setting a new client label from a trusted
procedure, however I just read in the original post that this was how the
current usecase of Joshua is set up. Suggestion withdrawn.

In the scenario of Joshua's security policy, it does not allow httpd to issue
SQL commands except for the trusted procedure that calls sepgsql_setcon()
according to the given credential. (Thus, we have no way to set an arbitrary
security label without credentials.) The security label being switched is
allowed to issue SQL commands with restricted privileges, and also allows
to translate into httpd's domain.
If we would not support sepgsql_setcon() in trusted procedure, we have to
allow httpd to translate an arbitrary label thus it also disallow to keep
connection because it should not revert the client label to httpd (it means
"restricted" users enable to switch someone arbitrary!)

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#38Robert Haas
robertmhaas@gmail.com
In reply to: Yeb Havinga (#33)
Re: [v9.2] Add GUC sepgsql.client_label

On Sat, Mar 10, 2012 at 4:35 PM, Yeb Havinga <yebhavinga@gmail.com> wrote:

The semantics are muddled because the client labels are mixed with labels
from trusted procedures. The stack you described upthread may also exhibit
surprising behaviour. If a trusted procedure is called, it's label is pushed
onto the stack.

What I was proposing is that it would *replace* the label on top of the stack.

Suppose it pushes another label by calling sepgsql_setcon().
In the stack case, this is now the top of the stack and the result of
sepgsql_get_client_label(). The procedure exists. What should now be the
result of sepgsql_get_client_label()? Since labels are managed by a stack,
we can only pop what's on top and need to pop twice,

The above avoids the need to pop twice.

so we've lost the label
pushed onto the stack by the trusted function, which means that trusted
procedures cannot be used to change client labels beyond their own scope,
but other functions would.

That's true, but I'm not convinced it's bad. I mean, if you instruct
the system to change security labels for the duration of a trusted
procedure, then it shouldn't be surprising that you end up with the
same security label after it exits that you had before entering it.
At the very least, it has the virtue of being consistent with other
things, like how GUCs behave. The current behavior is that you change
the context and you don't see the results of your own context change,
which seems far worse.

Maybe this semantics muddling of trusted procedures and setting the client
label can be avoided by denying changing the client label with
sepgsql_setcon() from a trusted procedure, which would also make some sense:

ERROR: cannot override the client label of a trusted procedure during
execution.

That also seems possibly reasonable.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#39Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#37)
Re: [v9.2] Add GUC sepgsql.client_label

On Mon, Mar 12, 2012 at 10:58 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

It is a practical reason. In case when httpd open the connection to PG and
set a suitable security label according to the given credential prior to launch
of user application, then keep this connection for upcoming request, it is
worthwhile to reset security label of the client.

But wait a minute - how is that any good? That allows the client to
pretty trivially circumvent the security restriction that we were
trying to impose by doing sepgsql_setcon() in the first place. It
doesn't matter how convenient it is if it's flagrantly insecure.

Am I missing something here?

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#40Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#39)
Re: [v9.2] Add GUC sepgsql.client_label

2012/3/12 Robert Haas <robertmhaas@gmail.com>:

On Mon, Mar 12, 2012 at 10:58 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

It is a practical reason. In case when httpd open the connection to PG and
set a suitable security label according to the given credential prior to launch
of user application, then keep this connection for upcoming request, it is
worthwhile to reset security label of the client.

But wait a minute - how is that any good?  That allows the client to
pretty trivially circumvent the security restriction that we were
trying to impose by doing sepgsql_setcon() in the first place.  It
doesn't matter how convenient it is if it's flagrantly insecure.

Am I missing something here?

It is a practical reason. If we would not support the reset feature,
the application has to know the security label of itself, as a target
label to be reverted. However, I'm not certain the status of script-
language binding of libselinux feature to obtain the self label,
although it is supported on Perl, Ruby and PHP (with extension
by myself) at least.

It seems to me a reasonable cost to track the original label to
eliminate a restriction of application side that tries to revert
the security label once switched.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#41Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#40)
Re: [v9.2] Add GUC sepgsql.client_label

On Mon, Mar 12, 2012 at 11:13 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/3/12 Robert Haas <robertmhaas@gmail.com>:

On Mon, Mar 12, 2012 at 10:58 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

It is a practical reason. In case when httpd open the connection to PG and
set a suitable security label according to the given credential prior to launch
of user application, then keep this connection for upcoming request, it is
worthwhile to reset security label of the client.

But wait a minute - how is that any good?  That allows the client to
pretty trivially circumvent the security restriction that we were
trying to impose by doing sepgsql_setcon() in the first place.  It
doesn't matter how convenient it is if it's flagrantly insecure.

Am I missing something here?

It is a practical reason. If we would not support the reset feature,
the application has to know the security label of itself, as a target
label to be reverted. However, I'm not certain the status of script-
language binding of libselinux feature to obtain the self label,
although it is supported on Perl, Ruby and PHP (with extension
by myself) at least.

You're still missing my point. The issue isn't the particular choice
of mechanism for reverting to the original security label; it's the
fact that such a thing would be possible at all.

Suppose that the connection starts out in context connection_pooler_t.
Based on the identity of the user, we transition to foo_t, bar_t, or
baz_t. If it's possible, by any method, for one of those contexts to
get back to connection_pooler_t, then we've got a problem. We give a
connection to user foo which is in foo_t; he transitions it back to
connection_pooler_t, then to bar_t, and usurps user bar's privileges.
Unless there's some way to prevent that, the only way to make this
secure is to make the transition to foo_t irreversible.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#42Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#41)
Re: [v9.2] Add GUC sepgsql.client_label

2012/3/12 Robert Haas <robertmhaas@gmail.com>:

On Mon, Mar 12, 2012 at 11:13 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/3/12 Robert Haas <robertmhaas@gmail.com>:

On Mon, Mar 12, 2012 at 10:58 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

It is a practical reason. In case when httpd open the connection to PG and
set a suitable security label according to the given credential prior to launch
of user application, then keep this connection for upcoming request, it is
worthwhile to reset security label of the client.

But wait a minute - how is that any good?  That allows the client to
pretty trivially circumvent the security restriction that we were
trying to impose by doing sepgsql_setcon() in the first place.  It
doesn't matter how convenient it is if it's flagrantly insecure.

Am I missing something here?

It is a practical reason. If we would not support the reset feature,
the application has to know the security label of itself, as a target
label to be reverted. However, I'm not certain the status of script-
language binding of libselinux feature to obtain the self label,
although it is supported on Perl, Ruby and PHP (with extension
by myself) at least.

You're still missing my point.  The issue isn't the particular choice
of mechanism for reverting to the original security label; it's the
fact that such a thing would be possible at all.

Suppose that the connection starts out in context connection_pooler_t.
 Based on the identity of the user, we transition to foo_t, bar_t, or
baz_t.  If it's possible, by any method, for one of those contexts to
get back to connection_pooler_t, then we've got a problem.  We give a
connection to user foo which is in foo_t; he transitions it back to
connection_pooler_t, then to bar_t, and usurps user bar's privileges.
Unless there's some way to prevent that, the only way to make this
secure is to make the transition to foo_t irreversible.

It is the reason why I advocate the idea to allow sepgsql_setcon()
inside of trusted-procedures.

The original use-case of Joshua does not allow connection_pooler_t
to execute any SQL commands except for invocation of a particular
trusted-procedures; that takes a secret credential as an argument,
then it switches the client label to foo_t, bar_t or baz_t according to
the supplied credential.
These labels are allowed to switch back to the original
connection_pooler_t, but it is unavailable to switch arbitrary label
without suitable credential.

Please also note that the reset of security label is handled as
a switch from the current one to the original one; that takes
permission check as normal manner.
So, it is an option to prevent to reset the client label to the original
one; that is allowed to switch arbitrary label, in an environment
without connection pooling.

Do we still have problematic scenario here?

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#43Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#42)
Re: [v9.2] Add GUC sepgsql.client_label

On Mon, Mar 12, 2012 at 12:30 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

Suppose that the connection starts out in context connection_pooler_t.
 Based on the identity of the user, we transition to foo_t, bar_t, or
baz_t.  If it's possible, by any method, for one of those contexts to
get back to connection_pooler_t, then we've got a problem.  We give a
connection to user foo which is in foo_t; he transitions it back to
connection_pooler_t, then to bar_t, and usurps user bar's privileges.
Unless there's some way to prevent that, the only way to make this
secure is to make the transition to foo_t irreversible.

It is the reason why I advocate the idea to allow sepgsql_setcon()
inside of trusted-procedures.

The original use-case of Joshua does not allow connection_pooler_t
to execute any SQL commands except for invocation of a particular
trusted-procedures; that takes a secret credential as an argument,
then it switches the client label to foo_t, bar_t or baz_t according to
the supplied credential.
These labels are allowed to switch back to the original
connection_pooler_t, but it is unavailable to switch arbitrary label
without suitable credential.

Oh, I get it.

Given that that's the intended use case, the current design does make
sense, but it seems awfully special-purpose. Not knowing that this is
what you had in mind, I never would have guessed the reason for all
this complexity. I worry that this is too much of a purpose-built
mechanism, and that nobody will ever be able to use it for much of
anything beyond the extremely specific use case that you've laid out
here. I think that, at the very least, the comments and documentation
need to make it clear that this is very deliberately intended to
modify only the toplevel security context of the session, which may be
different from the currently active context if a TP is in use; and
also that the change will apply to future transactions only if the
current transaction commits.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#44Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#43)
Re: [v9.2] Add GUC sepgsql.client_label

2012/3/12 Robert Haas <robertmhaas@gmail.com>:

On Mon, Mar 12, 2012 at 12:30 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

Suppose that the connection starts out in context connection_pooler_t.
 Based on the identity of the user, we transition to foo_t, bar_t, or
baz_t.  If it's possible, by any method, for one of those contexts to
get back to connection_pooler_t, then we've got a problem.  We give a
connection to user foo which is in foo_t; he transitions it back to
connection_pooler_t, then to bar_t, and usurps user bar's privileges.
Unless there's some way to prevent that, the only way to make this
secure is to make the transition to foo_t irreversible.

It is the reason why I advocate the idea to allow sepgsql_setcon()
inside of trusted-procedures.

The original use-case of Joshua does not allow connection_pooler_t
to execute any SQL commands except for invocation of a particular
trusted-procedures; that takes a secret credential as an argument,
then it switches the client label to foo_t, bar_t or baz_t according to
the supplied credential.
These labels are allowed to switch back to the original
connection_pooler_t, but it is unavailable to switch arbitrary label
without suitable credential.

Oh, I get it.

Given that that's the intended use case, the current design does make
sense, but it seems awfully special-purpose.  Not knowing that this is
what you had in mind, I never would have guessed the reason for all
this complexity.  I worry that this is too much of a purpose-built
mechanism, and that nobody will ever be able to use it for much of
anything beyond the extremely specific use case that you've laid out
here.  I think that, at the very least, the comments and documentation
need to make it clear that this is very deliberately intended to
modify only the toplevel security context of the session, which may be
different from the currently active context if a TP is in use; and
also that the change will apply to future transactions only if the
current transaction commits.

OK, I try to update the documentation and test cases with related
security policy, rather than the code base itself.

Please wait for a few days to update them.
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#45Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#44)
1 attachment(s)
Re: [v9.2] Add GUC sepgsql.client_label

2012/3/13 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/3/12 Robert Haas <robertmhaas@gmail.com>:

On Mon, Mar 12, 2012 at 12:30 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

Suppose that the connection starts out in context connection_pooler_t.
 Based on the identity of the user, we transition to foo_t, bar_t, or
baz_t.  If it's possible, by any method, for one of those contexts to
get back to connection_pooler_t, then we've got a problem.  We give a
connection to user foo which is in foo_t; he transitions it back to
connection_pooler_t, then to bar_t, and usurps user bar's privileges.
Unless there's some way to prevent that, the only way to make this
secure is to make the transition to foo_t irreversible.

It is the reason why I advocate the idea to allow sepgsql_setcon()
inside of trusted-procedures.

The original use-case of Joshua does not allow connection_pooler_t
to execute any SQL commands except for invocation of a particular
trusted-procedures; that takes a secret credential as an argument,
then it switches the client label to foo_t, bar_t or baz_t according to
the supplied credential.
These labels are allowed to switch back to the original
connection_pooler_t, but it is unavailable to switch arbitrary label
without suitable credential.

Oh, I get it.

Given that that's the intended use case, the current design does make
sense, but it seems awfully special-purpose.  Not knowing that this is
what you had in mind, I never would have guessed the reason for all
this complexity.  I worry that this is too much of a purpose-built
mechanism, and that nobody will ever be able to use it for much of
anything beyond the extremely specific use case that you've laid out
here.  I think that, at the very least, the comments and documentation
need to make it clear that this is very deliberately intended to
modify only the toplevel security context of the session, which may be
different from the currently active context if a TP is in use; and
also that the change will apply to future transactions only if the
current transaction commits.

OK, I try to update the documentation and test cases with related
security policy, rather than the code base itself.

The attached patch contains the documentation updates and test
cases that simulate a typical behavior of connection pooling
software.

In this test case, sepgsql_regtest_pool_t is only allowed to translate
to sepgsql_regtest_(foo|var)_t via trusted procedure, and these
domains are unavailable to reference the tables related to other
domains. It is according to the original explanation I got from Joshua
Brindle. In actual cases, the trusted procedure will take an argument
of the credential being stored within CAC card.

I didn't touch the code portion from the previous version.

If it is ready to commit, please remember the credit to Yeb's volunteer
on this patch.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.2-sepgsql-setcon.part2-v6.patchapplication/octet-stream; name=pgsql-v9.2-sepgsql-setcon.part2-v6.patchDownload
 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 bac169f..f9587de 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
 --
@@ -100,6 +126,325 @@ SELECT sepgsql_getcon();	-- client's label must be restored
 (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
 --
 SELECT sepgsql_getcon();	-- confirm client privilege
@@ -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 340bec6..deadd88 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);
 }
 
 /*
@@ -361,6 +534,27 @@ sepgsql_getcon(PG_FUNCTION_ARGS)
 }
 
 /*
+ * 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)
  *
  * It translate the given qualified MLS/MCS range into raw format
diff --git a/contrib/sepgsql/selinux.c b/contrib/sepgsql/selinux.c
index 8819b8c..0652e29 100644
--- a/contrib/sepgsql/selinux.c
+++ b/contrib/sepgsql/selinux.c
@@ -46,6 +46,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 a8fe247..d872945 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 0100a09..708d4ee 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 45ffe31..917d12d 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 2b18412..e63b5f6 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
 --
@@ -69,6 +100,129 @@ 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
 --
 -- @SECURITY-CONTEXT=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c255
@@ -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 dbddf86..56c465b 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
 </screen>
 
   <para>
@@ -526,6 +526,68 @@ postgres=# SELECT cid, cname, show_credit(cid) FROM customer;
   </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>
     We reject the <xref linkend="sql-load"> command across the board, because
@@ -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">
#46Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#45)
Re: [v9.2] Add GUC sepgsql.client_label

On Wed, Mar 14, 2012 at 11:10 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

If it is ready to commit, please remember the credit to Yeb's volunteer
on this patch.

Done.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#47Yeb Havinga
yebhavinga@gmail.com
In reply to: Robert Haas (#46)
Re: [v9.2] Add GUC sepgsql.client_label

On 2012-03-15 21:45, Robert Haas wrote:

On Wed, Mar 14, 2012 at 11:10 AM, Kohei KaiGai<kaigai@kaigai.gr.jp> wrote:

If it is ready to commit, please remember the credit to Yeb's volunteer
on this patch.

Done.

In the patch with copy-editing documentation following that commit, at
"in at their option", s/in// ? Also 'rather than .. as mandated by the
system': I'm having trouble parsing 'as'. It is also unclear to me what
'system' means: selinux or PostgreSQL, or both? I suspect it is
PostgreSQL, since selinux is still enforcing / 'mandating' it's policy.
What about "rather than that the switch is controlled by the PostgreSQL
server, as in the case of a trusted procedure."

+    Dynamic domain transitions should be considered carefully, because they
+    allow users to switch their label, and therefore their privileges, in
+    at their option, rather than (as in the case of a trusted procedure)
+    as mandated by the system.

--
Yeb Havinga
http://www.mgrid.net/
Mastering Medical Data

#48Robert Haas
robertmhaas@gmail.com
In reply to: Yeb Havinga (#47)
Re: [v9.2] Add GUC sepgsql.client_label

On Fri, Mar 16, 2012 at 3:44 AM, Yeb Havinga <yebhavinga@gmail.com> wrote:

In the patch with copy-editing documentation following that commit, at "in
at their option", s/in// ?

Oh, yeah. Oops. Thanks.

Also 'rather than .. as mandated by the system':
I'm having trouble parsing 'as'. It is also unclear to me what 'system'
means: selinux or PostgreSQL, or both? I suspect it is PostgreSQL, since
selinux is still enforcing / 'mandating' it's policy. What about "rather
than that the switch is controlled by the PostgreSQL server, as in the case
of a trusted procedure."

Well, I think it's both. PostgreSQL is responsible for enforcing
privileges on database objects, but it relies on SE-Linux to tell it
whether a given access is allowable. So, from PostgreSQL's point of
view, it's delegating the decision to SE-Linux. But SE-Linux views
itself as a mechanism for enforcing a system-wide security policy, so
views PostgreSQL as an instrument for carrying out its access control
goals. I don't know how to disentangle that. I'm actually not
entirely sure that I even believe the underlying sentiment that
dynamic transitions are dangerous. Maybe KaiGai could comment further
on what we should be trying to convey here.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#49Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#48)
Re: [v9.2] Add GUC sepgsql.client_label

2012/3/20 Robert Haas <robertmhaas@gmail.com>:

On Fri, Mar 16, 2012 at 3:44 AM, Yeb Havinga <yebhavinga@gmail.com> wrote:

In the patch with copy-editing documentation following that commit, at "in
at their option", s/in// ?

Oh, yeah.  Oops.  Thanks.

Also 'rather than .. as mandated by the system':
I'm having trouble parsing 'as'. It is also unclear to me what 'system'
means: selinux or PostgreSQL, or both? I suspect it is PostgreSQL, since
selinux is still enforcing / 'mandating' it's policy. What about "rather
than that the switch is controlled by the PostgreSQL server, as in the case
of a trusted procedure."

Well, I think it's both.  PostgreSQL is responsible for enforcing
privileges on database objects, but it relies on SE-Linux to tell it
whether a given access is allowable.  So, from PostgreSQL's point of
view, it's delegating the decision to SE-Linux.  But SE-Linux views
itself as a mechanism for enforcing a system-wide security policy, so
views PostgreSQL as an instrument for carrying out its access control
goals.  I don't know how to disentangle that.  I'm actually not
entirely sure that I even believe the underlying sentiment that
dynamic transitions are dangerous.  Maybe KaiGai could comment further
on what we should be trying to convey here.

The reason why dynamic domain transition should be configured
carefully is that it partially allows users to switch their own privileges
in discretionary way, unlike trusted procedure.

The original model of selinux on operating system assumes all the
domain transition shall happen on execve(2) time, but it made clear
some sort of application is not happy with traditional fork - exec
lifecycle, such as web server, connection pooling software, or others.

Even as they perform according to the operations from users,
it does not fork - exec itself because of some reason, typically
performance. One point we should focus on is these applications
have relatively trustable portion and untrustable one.

The dynamic domain transition was designed to "restrict" privileges
more than the current one on the trustable portion, prior to launch
untrustable one. So, it never intend to switch client domain with
100% arbitrary. Its bottom line is restricted with the security policy;
that explicitly describes the range of domains being allowed to
translate.

So, we will be able to conclude dynamic domain transition is
harmless as long as it works to reduce privileges; that should
be guaranteed with the security policy.
It also means sepgsql_setcon() is harmless as long as it works
according to the decision of SELinux.

The connection pooling software scenario using trusted procedure
might be a bit confusing. In this case, the client domain is once
switched to the trusted one with mandatory way, then it switches
to more restricted domain in arbitrary way; thus, it is not allowed
to promote its privileges in arbitrary way.
We assume the trusted procedure is a enough small portion to
ensure bug or vulnerability free.

Joshua, please add some comments, if you have.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#50Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#49)
Re: [v9.2] Add GUC sepgsql.client_label

On Wed, Mar 21, 2012 at 6:07 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

The reason why dynamic domain transition should be configured
carefully is that it partially allows users to switch their own privileges
in discretionary way, unlike trusted procedure.

The original model of selinux on operating system assumes all the
domain transition shall happen on execve(2) time, but it made clear
some sort of application is not happy with traditional fork - exec
lifecycle, such as web server, connection pooling software, or others.

Even as they perform according to the operations from users,
it does not fork - exec itself because of some reason, typically
performance. One point we should focus on is these applications
have relatively trustable portion and untrustable one.

The dynamic domain transition was designed to "restrict" privileges
more than the current one on the trustable portion, prior to launch
untrustable one. So, it never intend to switch client domain with
100% arbitrary. Its bottom line is restricted with the security policy;
that explicitly describes the range of domains being allowed to
translate.

So, we will be able to conclude dynamic domain transition is
harmless as long as it works to reduce privileges; that should
be guaranteed with the security policy.
It also means sepgsql_setcon() is harmless as long as it works
according to the decision of SELinux.

The connection pooling software scenario using trusted procedure
might be a bit confusing. In this case, the client domain is once
switched to the trusted one with mandatory way, then it switches
to more restricted domain in arbitrary way; thus, it is not allowed
to promote its privileges in arbitrary way.
We assume the trusted procedure is a enough small portion to
ensure bug or vulnerability free.

Joshua, please add some comments, if you have.

I guess my feeling on this is that the warning in the documentation
isn't really helping anything here. I mean, we don't need to document
that allowing people to give themselves more privileges is a security
hole; that much is obvious.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company