Label switcher function

Started by KaiGai Koheiabout 15 years ago13 messages
#1KaiGai Kohei
kaigai@kaigai.gr.jp
1 attachment(s)

The attached patch allows the security label provider to switch
security label of the client during execution of certain functions.
I named it as "label switcher function"; also called as "trusted-
procedure" in SELinux community.

This feature is quite similar idea toward security definer function,
or set-uid program on operating system. It allows label providers
to switch its internal state that holds security label of the
client, then restore it.
If and when a label provider said the function being invoked is
a label-switcher, fmgr_security_definer() traps this invocation
and set some states just before actual invocations.

We added three new hooks for security label provider.
The get_client_label and set_client_label allows the PG core to
save and restore security label of the client; which is mostly
just an internal state of plugin module.
And, the get_switched_label shall return NULL or a valid label
if the supplied function is a label switcher. It also informs
the PG core whether the function is switcher or not.

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

Attachments:

pgsql-switcher-function.1.patchapplication/octect-stream; name=pgsql-switcher-function.1.patchDownload
diff --git a/contrib/dummy_seclabel/dummy_seclabel.c b/contrib/dummy_seclabel/dummy_seclabel.c
index 8bd50a3..ff7b22c 100644
--- a/contrib/dummy_seclabel/dummy_seclabel.c
+++ b/contrib/dummy_seclabel/dummy_seclabel.c
@@ -14,12 +14,60 @@
 
 #include "commands/seclabel.h"
 #include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
 
 PG_MODULE_MAGIC;
 
+PG_FUNCTION_INFO_V1(dummy_client_label);
+
 /* Entrypoint of the module */
 void _PG_init(void);
 
+static const char *client_label = NULL;
+
+/* SQL function to show client label */
+Datum dummay_client_label(PG_FUNCTION_ARGS);
+
+static const char *
+dummy_get_client_label(void)
+{
+	return client_label;
+}
+
+static void
+dummy_set_client_label(const char *seclabel)
+{
+	client_label = seclabel;
+}
+
+static const char *
+dummy_get_switched_label(Oid procOid)
+{
+	char   *proname = get_func_name(procOid);
+	char   *result = NULL;
+
+	/*
+	 * It assumes a function whose name contains 'trusted' means
+	 * a label switcher function.
+	 */
+	if (strstr(proname, "trusted") != NULL)
+		result = "trusted";
+
+	pfree(proname);
+
+	return result;
+}
+
+Datum
+dummy_client_label(PG_FUNCTION_ARGS)
+{
+	if (!client_label)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text(client_label));
+}
+
 static void
 dummy_object_relabel(const ObjectAddress *object, const char *seclabel)
 {
@@ -45,5 +93,17 @@ dummy_object_relabel(const ObjectAddress *object, const char *seclabel)
 void
 _PG_init(void)
 {
-	register_label_provider("dummy", dummy_object_relabel);
+	/*
+	 * XXX - we assume this module is loaded during regression test
+	 * using LOAD command. In normal label providers which is installed
+	 * by shared_preload_libraries, the client label shall be initialized
+	 * on the authentication hook.
+	 */
+	client_label = (superuser() ? "secret" : "unclassified");
+
+	register_label_provider("dummy",
+							dummy_object_relabel,
+							dummy_get_switched_label,
+							dummy_get_client_label,
+							dummy_set_client_label);
 }
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d2e2e11..5e71b62 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -30,6 +30,7 @@
 #include "catalog/namespace.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/seclabel.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
@@ -141,6 +142,7 @@ typedef struct TransactionStateData
 	int			maxChildXids;	/* allocated size of childXids[] */
 	Oid			prevUser;		/* previous CurrentUserId setting */
 	int			prevSecContext; /* previous SecurityRestrictionContext */
+	List	   *prevSecLabels;	/* previous security label of client */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
 	struct TransactionStateData *parent;		/* back link to parent */
@@ -170,6 +172,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* allocated size of childXids[] */
 	InvalidOid,					/* previous CurrentUserId setting */
 	0,							/* previous SecurityRestrictionContext */
+	NIL,						/* previous security label of client */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
 	NULL						/* link to parent state block */
@@ -1694,6 +1697,9 @@ StartTransaction(void)
 	/* SecurityRestrictionContext should never be set outside a transaction */
 	Assert(s->prevSecContext == 0);
 
+	/* Save current security label of the client */
+	s->prevSecLabels = GetClientSecLabels();
+
 	/*
 	 * initialize other subsystems for new transaction
 	 */
@@ -2198,6 +2204,9 @@ AbortTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Reset security labels of the client */
+	SetClientSecLabels(s->prevSecLabels);
+
 	/*
 	 * do abort processing
 	 */
@@ -4042,6 +4051,9 @@ AbortSubTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Reset security labels of the client */
+	SetClientSecLabels(s->prevSecLabels);
+
 	/*
 	 * We can skip all this stuff if the subxact failed before creating a
 	 * ResourceOwner...
@@ -4181,6 +4193,7 @@ PushTransaction(void)
 	s->state = TRANS_DEFAULT;
 	s->blockState = TBLOCK_SUBBEGIN;
 	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
+	s->prevSecLabels = GetClientSecLabels();
 	s->prevXactReadOnly = XactReadOnly;
 
 	CurrentTransactionState = s;
diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c
index 762bbae..7ac2547 100644
--- a/src/backend/commands/seclabel.c
+++ b/src/backend/commands/seclabel.c
@@ -35,11 +35,20 @@ static void CheckAttributeSecLabel(Relation relation);
 typedef struct
 {
 	const char *provider_name;
-	check_object_relabel_type	hook;
+	check_object_relabel_type	relabel_hook;
+	get_switched_label_type		get_switched_hook;
+	get_client_label_type		get_client_hook;
+	set_client_label_type		set_client_hook;
 } LabelProvider;
 
 static List *label_provider_list = NIL;
 
+typedef struct
+{
+	const char *provider_name;
+	const char *client_label;
+} ClientLabel;
+
 /*
  * ExecSecLabelStmt --
  *
@@ -145,7 +154,7 @@ ExecSecLabelStmt(SecLabelStmt *stmt)
 	}
 
 	/* Provider gets control here, may throw ERROR to veto new label. */
-	(*provider->hook)(&address, stmt->label);
+	(*provider->relabel_hook)(&address, stmt->label);
 
 	/* Apply new label. */
 	SetSecurityLabel(&address, provider->provider_name, stmt->label);
@@ -372,8 +381,134 @@ CheckAttributeSecLabel(Relation relation)
 						RelationGetRelationName(relation))));
 }
 
+/*
+ * GetSwitchedSecLabels
+ *
+ * It returns a list of security labels that is applied during execution
+ * of the supplied procedure. If no label provider considered the function
+ * as a label-switcher, it returns NIL.
+ */
+List *
+GetSwitchedSecLabels(Oid procOid, MemoryContext cxt)
+{
+	MemoryContext	oldcxt;
+	ListCell	   *lcp;
+	List		   *results = NIL;
+
+	oldcxt = MemoryContextSwitchTo(cxt);
+
+	foreach (lcp, label_provider_list)
+    {
+		LabelProvider  *lp = lfirst(lcp);
+		ClientLabel	   *cl;
+		const char	   *label;
+
+		if (!lp->get_switched_hook)
+			continue;
+
+		label = (*lp->get_switched_hook)(procOid);
+		if (!label)
+			continue;
+
+		cl = palloc(sizeof(ClientLabel));
+		cl->provider_name = lp->provider_name;
+		cl->client_label = label;
+
+		results = lappend(results, cl);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+
+	return results;
+}
+
+/*
+ * ProcIsLabelSwitcher
+ *
+ * True shall be returned, if one of the label provider considers the given
+ * function is a label-switcher. Elsewhere, false shall be returned; that
+ * includes a case when no label provider is installed.
+ */
+bool
+ProcIsLabelSwitcher(Oid procOid)
+{
+	List   *temp = GetSwitchedSecLabels(procOid, CurrentMemoryContext);
+	bool	result;
+
+	result = (temp != NIL ? true : false);
+
+	list_free_deep(temp);
+
+	return result;
+}
+
+/*
+ * GetClientSecLabels
+ *
+ * It returns a list of security label assigned to the client for all the
+ * label providers.
+ */
+List *
+GetClientSecLabels(void)
+{
+	ListCell   *lcp;
+	List	   *results = NIL;
+
+	foreach (lcp, label_provider_list)
+	{
+		LabelProvider  *lp = lfirst(lcp);
+		ClientLabel	   *cl;
+
+		if (!lp->get_client_hook)
+			continue;
+
+		cl = palloc(sizeof(ClientLabel));
+		cl->provider_name = lp->provider_name;
+		cl->client_label = (*lp->get_client_hook)();
+
+		results = lappend(results, cl);
+	}
+	return results;
+}
+
+/*
+ * SetClientSecLabels
+ *
+ * It restores a list of security labels saved at GetClientSecLabels() or
+ * GetSwitchedSecLabels().
+ */
+void
+SetClientSecLabels(List *saved_labels)
+{
+	ListCell   *lcp;
+	ListCell   *lcl;
+
+	foreach (lcp, label_provider_list)
+	{
+		LabelProvider  *lp = lfirst(lcp);
+
+		if (!lp->set_client_hook)
+			continue;
+
+		foreach (lcl, saved_labels)
+		{
+			ClientLabel	   *cl = lfirst(lcl);
+
+			if (strcmp(lp->provider_name, cl->provider_name) == 0)
+			{
+				(*lp->set_client_hook)(cl->client_label);
+				break;
+			}
+		}
+	}
+}
+
 void
-register_label_provider(const char *provider_name, check_object_relabel_type hook)
+register_label_provider(const char *provider_name,
+						check_object_relabel_type relabel_hook,
+						get_switched_label_type get_switched_hook,
+						get_client_label_type get_client_hook,
+						set_client_label_type set_client_hook)
 {
 	LabelProvider  *provider;
 	MemoryContext	oldcxt;
@@ -381,7 +516,10 @@ register_label_provider(const char *provider_name, check_object_relabel_type hoo
 	oldcxt = MemoryContextSwitchTo(TopMemoryContext);
 	provider = palloc(sizeof(LabelProvider));
 	provider->provider_name = pstrdup(provider_name);
-	provider->hook = hook;
+	provider->relabel_hook = relabel_hook;
+	provider->get_switched_hook = get_switched_hook;
+	provider->get_client_hook = get_client_hook;
+	provider->set_client_hook = set_client_hook;
 	label_provider_list = lappend(label_provider_list, provider);
 	MemoryContextSwitchTo(oldcxt);
 }
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index de2e66b..cc42702 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -24,6 +24,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
+#include "commands/seclabel.h"
 #include "executor/executor.h"
 #include "executor/functions.h"
 #include "miscadmin.h"
@@ -3721,6 +3722,10 @@ inline_function(Oid funcid, Oid result_type, List *args,
 	if (pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
 		return NULL;
 
+	/* Check whether the function is not label switcher */
+	if (ProcIsLabelSwitcher(funcid))
+		return NULL;
+
 	/*
 	 * Make a temporary memory context, so that we don't leak all the stuff
 	 * that parsing might create.
@@ -4153,6 +4158,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	if (pg_proc_aclcheck(func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
 		return NULL;
 
+	/* Check whether the function is not label switcher */
+	if (ProcIsLabelSwitcher(func_oid))
+		return NULL;
+
 	/*
 	 * OK, let's take a look at the function's pg_proc entry.
 	 */
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 1c9d2c2..7671db3 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -18,6 +18,7 @@
 #include "access/tuptoaster.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_proc.h"
+#include "commands/seclabel.h"
 #include "executor/functions.h"
 #include "executor/spi.h"
 #include "lib/stringinfo.h"
@@ -230,7 +231,8 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
 	 */
 	if (!ignore_security &&
 		(procedureStruct->prosecdef ||
-		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig)))
+		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) ||
+		 ProcIsLabelSwitcher(functionId)))
 	{
 		finfo->fn_addr = fmgr_security_definer;
 		finfo->fn_stats = TRACK_FUNC_ALL;		/* ie, never track */
@@ -857,6 +859,7 @@ struct fmgr_security_definer_cache
 	FmgrInfo	flinfo;			/* lookup info for target function */
 	Oid			userid;			/* userid to set, or InvalidOid */
 	ArrayType  *proconfig;		/* GUC values to set, or NULL */
+	List	   *seclabels;		/* security label to switch, or NIL */
 };
 
 /*
@@ -878,6 +881,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 	Oid			save_userid;
 	int			save_sec_context;
 	volatile int save_nestlevel;
+	List	   *save_sec_labels;
 	PgStat_FunctionCallUsage fcusage;
 
 	if (!fcinfo->flinfo->fn_extra)
@@ -914,6 +918,8 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 			MemoryContextSwitchTo(oldcxt);
 		}
 
+		fcache->seclabels = GetSwitchedSecLabels(fcinfo->flinfo->fn_oid,
+												 fcinfo->flinfo->fn_mcxt);
 		ReleaseSysCache(tuple);
 
 		fcinfo->flinfo->fn_extra = fcache;
@@ -940,6 +946,14 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 						GUC_ACTION_SAVE);
 	}
 
+	if (fcache->seclabels != NIL)
+	{
+		save_sec_labels = GetClientSecLabels();
+		SetClientSecLabels(fcache->seclabels);
+	}
+	else
+		save_sec_labels = NIL;	/* keep compiler quiet */
+
 	/*
 	 * We don't need to restore GUC or userid settings on error, because the
 	 * ensuing xact or subxact abort will do that.	The PG_TRY block is only
@@ -978,6 +992,11 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 		AtEOXact_GUC(true, save_nestlevel);
 	if (OidIsValid(fcache->userid))
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+	if (fcache->seclabels)
+	{
+		SetClientSecLabels(save_sec_labels);
+		list_free_deep(save_sec_labels);
+	}
 
 	return result;
 }
diff --git a/src/include/commands/seclabel.h b/src/include/commands/seclabel.h
index 4c3854e..b8985ba 100644
--- a/src/include/commands/seclabel.h
+++ b/src/include/commands/seclabel.h
@@ -12,6 +12,7 @@
 #include "catalog/objectaddress.h"
 #include "nodes/primnodes.h"
 #include "nodes/parsenodes.h"
+#include "utils/memutils.h"
 
 /*
  * Internal APIs
@@ -29,7 +30,22 @@ extern void ExecSecLabelStmt(SecLabelStmt *stmt);
 
 typedef void (*check_object_relabel_type)(const ObjectAddress *object,
 										  const char *seclabel);
-extern void register_label_provider(const char *provider,
-								    check_object_relabel_type hook);
 
+/*
+ * Trusted-Procedure Support
+ */
+extern List	   *GetSwitchedSecLabels(Oid procOid, MemoryContext cxt);
+extern bool		ProcIsLabelSwitcher(Oid procOid);
+extern List	   *GetClientSecLabels(void);
+extern void		SetClientSecLabels(List *savedLabels);
+
+typedef const char *(*get_switched_label_type)(Oid procOid);
+typedef const char *(*get_client_label_type)(void);
+typedef void (*set_client_label_type)(const char *seclabel);
+
+extern void register_label_provider(const char *provider,
+								    check_object_relabel_type relabel_hook,
+									get_switched_label_type get_switch_hook,
+									get_client_label_type get_client_hook,
+									set_client_label_type set_client_hook);
 #endif	/* SECLABEL_H */
diff --git a/src/test/regress/input/security_label.source b/src/test/regress/input/security_label.source
index 810a721..5fdb466 100644
--- a/src/test/regress/input/security_label.source
+++ b/src/test/regress/input/security_label.source
@@ -24,6 +24,9 @@ CREATE DOMAIN seclabel_domain AS text;
 ALTER TABLE seclabel_tbl1 OWNER TO seclabel_user1;
 ALTER TABLE seclabel_tbl2 OWNER TO seclabel_user2;
 
+DROP FUNCTION IF EXISTS dummy_trusted_func();
+DROP FUNCTION IF EXISTS dummy_regular_func();
+
 RESET client_min_messages;
 
 --
@@ -37,6 +40,9 @@ SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified';			-- fail
 -- Load dummy external security provider
 LOAD '@libdir@/dummy_seclabel@DLSUFFIX@';
 
+CREATE OR REPLACE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c'
+    AS '@libdir@/dummy_seclabel@DLSUFFIX@', 'dummy_client_label';
+
 --
 -- Test of SECURITY LABEL statement with a plugin
 --
@@ -70,7 +76,18 @@ SELECT objtype, objname, provider, label FROM pg_seclabels
 SECURITY LABEL ON LANGUAGE plpgsql IS NULL;						-- OK
 SECURITY LABEL ON SCHEMA public IS NULL;						-- OK
 
+-- test for label switcher function
+RESET SESSION AUTHORIZATION;
+
+CREATE OR REPLACE FUNCTION dummy_trusted_func() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+CREATE OR REPLACE FUNCTION dummy_regular_func() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+
+SELECT dummy_trusted_func(), dummy_regular_func();
+
 -- clean up objects
+RESET SESSION AUTHORIZATION;
 DROP FUNCTION seclabel_four();
 DROP DOMAIN seclabel_domain;
 DROP VIEW seclabel_view1;
@@ -78,6 +95,9 @@ DROP TABLE seclabel_tbl1;
 DROP TABLE seclabel_tbl2;
 DROP USER seclabel_user1;
 DROP USER seclabel_user2;
+DROP FUNCTION dummy_trusted_func();
+DROP FUNCTION dummy_regular_func();
+DROP FUNCTION dummy_client_label();
 
 -- make sure we don't have any leftovers
 SELECT objtype, objname, provider, label FROM pg_seclabels
diff --git a/src/test/regress/output/security_label.source b/src/test/regress/output/security_label.source
index 4bc803d..3356edb 100644
--- a/src/test/regress/output/security_label.source
+++ b/src/test/regress/output/security_label.source
@@ -17,6 +17,8 @@ CREATE FUNCTION seclabel_four() RETURNS integer AS $$SELECT 4$$ language sql;
 CREATE DOMAIN seclabel_domain AS text;
 ALTER TABLE seclabel_tbl1 OWNER TO seclabel_user1;
 ALTER TABLE seclabel_tbl2 OWNER TO seclabel_user2;
+DROP FUNCTION IF EXISTS dummy_trusted_func();
+DROP FUNCTION IF EXISTS dummy_regular_func();
 RESET client_min_messages;
 --
 -- Test of SECURITY LABEL statement without a plugin
@@ -30,7 +32,9 @@ ERROR:  no security label providers have been loaded
 SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified';			-- fail
 ERROR:  no security label providers have been loaded
 -- Load dummy external security provider
-LOAD '@abs_builddir@/dummy_seclabel@DLSUFFIX@';
+LOAD '@libdir@/dummy_seclabel@DLSUFFIX@';
+CREATE OR REPLACE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c'
+    AS '@libdir@/dummy_seclabel@DLSUFFIX@', 'dummy_client_label';
 --
 -- Test of SECURITY LABEL statement with a plugin
 --
@@ -75,7 +79,20 @@ SELECT objtype, objname, provider, label FROM pg_seclabels
 
 SECURITY LABEL ON LANGUAGE plpgsql IS NULL;						-- OK
 SECURITY LABEL ON SCHEMA public IS NULL;						-- OK
+-- test for label switcher function
+RESET SESSION AUTHORIZATION;
+CREATE OR REPLACE FUNCTION dummy_trusted_func() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+CREATE OR REPLACE FUNCTION dummy_regular_func() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+SELECT dummy_trusted_func(), dummy_regular_func();
+ dummy_trusted_func | dummy_regular_func 
+--------------------+--------------------
+ trusted            | secret
+(1 row)
+
 -- clean up objects
+RESET SESSION AUTHORIZATION;
 DROP FUNCTION seclabel_four();
 DROP DOMAIN seclabel_domain;
 DROP VIEW seclabel_view1;
@@ -83,6 +100,9 @@ DROP TABLE seclabel_tbl1;
 DROP TABLE seclabel_tbl2;
 DROP USER seclabel_user1;
 DROP USER seclabel_user2;
+DROP FUNCTION dummy_trusted_func();
+DROP FUNCTION dummy_regular_func();
+DROP FUNCTION dummy_client_label();
 -- make sure we don't have any leftovers
 SELECT objtype, objname, provider, label FROM pg_seclabels
 	ORDER BY objtype, objname;
#2Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#1)
Re: Label switcher function

2010/11/12 KaiGai Kohei <kaigai@kaigai.gr.jp>:

The attached patch allows the security label provider to switch
security label of the client during execution of certain functions.
I named it as "label switcher function"; also called as "trusted-
procedure" in SELinux community.

This feature is quite similar idea toward security definer function,
or set-uid program on operating system. It allows label providers
to switch its internal state that holds security label of the
client, then restore it.
If and when a label provider said the function being invoked is
a label-switcher, fmgr_security_definer() traps this invocation
and set some states just before actual invocations.

We added three new hooks for security label provider.
The get_client_label and set_client_label allows the PG core to
save and restore security label of the client; which is mostly
just an internal state of plugin module.
And, the get_switched_label shall return NULL or a valid label
if the supplied function is a label switcher. It also informs
the PG core whether the function is switcher or not.

I don't see why the plugin needs to expose the label stack to core PG.
If the plugin needs a label stack, it can do that all on its own. I
see that we need the hooks to allow the plugin to selectively disable
inlining and to gain control when function execution starts and ends
(or aborts) but I don't think the exact manipulations that the plugin
chooses to do at that point need to be visible to core PG.

For SE-Linux, how do you intend to determine whether or not the
function is a trusted procedure? Will that be a function of the
security label applied to it?

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

#3KaiGai Kohei
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#2)
Re: Label switcher function

(2010/11/14 11:19), Robert Haas wrote:

2010/11/12 KaiGai Kohei<kaigai@kaigai.gr.jp>:

The attached patch allows the security label provider to switch
security label of the client during execution of certain functions.
I named it as "label switcher function"; also called as "trusted-
procedure" in SELinux community.

This feature is quite similar idea toward security definer function,
or set-uid program on operating system. It allows label providers
to switch its internal state that holds security label of the
client, then restore it.
If and when a label provider said the function being invoked is
a label-switcher, fmgr_security_definer() traps this invocation
and set some states just before actual invocations.

We added three new hooks for security label provider.
The get_client_label and set_client_label allows the PG core to
save and restore security label of the client; which is mostly
just an internal state of plugin module.
And, the get_switched_label shall return NULL or a valid label
if the supplied function is a label switcher. It also informs
the PG core whether the function is switcher or not.

I don't see why the plugin needs to expose the label stack to core PG.
If the plugin needs a label stack, it can do that all on its own. I
see that we need the hooks to allow the plugin to selectively disable
inlining and to gain control when function execution starts and ends
(or aborts) but I don't think the exact manipulations that the plugin
chooses to do at that point need to be visible to core PG.

Hmm. I designed this patch according to the implementation of existing
security definer function, but it is not a only design.

Does the "label stack" means that this patch touches xact.c, doesn't it?
Yes, if we have above three hooks around function calls, the core PG
does not need to manage a label stack.

However, I want fmgr_security_definer_cache to have a field to save
private opaque data, because it is not a very-light step to ask SE-Linux
whether the function is trusted-procedure and to allocate a string to
be applied during execution, although switching is a very-light step.
So, I want to compute it at first time of the function calls, like as
security definer function checks syscache at once.

Of course, it is a private opaque data, it will be open for other usage.

For SE-Linux, how do you intend to determine whether or not the
function is a trusted procedure? Will that be a function of the
security label applied to it?

When the function being invoked has a special security label with
a "type_transition" rule on the current client's label in the
security policy, SE-Linux decides the function is trusted procedure.

In other words, we can know whether or not the function is a trusted
procedure by asking to the security policy. It is a task of the plugin.

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

#4KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: KaiGai Kohei (#3)
1 attachment(s)
Re: Label switcher function

I revised my patch as I attached.

The hook function is modified and consolidated as follows:

typedef enum FunctionCallEventType
{
FCET_BE_HOOKED,
FCET_PREPARE,
FCET_START,
FCET_END,
FCET_ABORT,
} FunctionCallEventType;

typedef Datum (*function_call_event_type)(Oid functionId,
FunctionCallEventType event,
Datum event_arg);
extern PGDLLIMPORT function_call_event_type function_call_event_hook;

Unlike the subject of this e-mail, now it does not focus on only switching
security labels during execution of a certain functions.
For example, we may use this hook to track certain functions for security
auditing, performance tuning, and others.

In the case of SE-PgSQL, it shall return BoolGetDatum(true), if the target
function is configured as a trusted procedure, then, this invocation will
be hooked by fmgr_security_definer. In the first call, it shall compute
the security context to be assigned during execution on FCET_PREPARE event.
Then, it switches to the computed label on the FCET_START event, and
restore it on the FCET_END or ECET_ABORT event.

I also fixed up regression test, dummy_seclabel module and its
documentation as Robert pointed out in another topic.

Thanks,

(2010/11/14 13:16), KaiGai Kohei wrote:

(2010/11/14 11:19), Robert Haas wrote:

2010/11/12 KaiGai Kohei<kaigai@kaigai.gr.jp>:

The attached patch allows the security label provider to switch
security label of the client during execution of certain functions.
I named it as "label switcher function"; also called as "trusted-
procedure" in SELinux community.

This feature is quite similar idea toward security definer function,
or set-uid program on operating system. It allows label providers
to switch its internal state that holds security label of the
client, then restore it.
If and when a label provider said the function being invoked is
a label-switcher, fmgr_security_definer() traps this invocation
and set some states just before actual invocations.

We added three new hooks for security label provider.
The get_client_label and set_client_label allows the PG core to
save and restore security label of the client; which is mostly
just an internal state of plugin module.
And, the get_switched_label shall return NULL or a valid label
if the supplied function is a label switcher. It also informs
the PG core whether the function is switcher or not.

I don't see why the plugin needs to expose the label stack to core PG.
If the plugin needs a label stack, it can do that all on its own. I
see that we need the hooks to allow the plugin to selectively disable
inlining and to gain control when function execution starts and ends
(or aborts) but I don't think the exact manipulations that the plugin
chooses to do at that point need to be visible to core PG.

Hmm. I designed this patch according to the implementation of existing
security definer function, but it is not a only design.

Does the "label stack" means that this patch touches xact.c, doesn't it?
Yes, if we have above three hooks around function calls, the core PG
does not need to manage a label stack.

However, I want fmgr_security_definer_cache to have a field to save
private opaque data, because it is not a very-light step to ask SE-Linux
whether the function is trusted-procedure and to allocate a string to
be applied during execution, although switching is a very-light step.
So, I want to compute it at first time of the function calls, like as
security definer function checks syscache at once.

Of course, it is a private opaque data, it will be open for other usage.

For SE-Linux, how do you intend to determine whether or not the
function is a trusted procedure? Will that be a function of the
security label applied to it?

When the function being invoked has a special security label with
a "type_transition" rule on the current client's label in the
security policy, SE-Linux decides the function is trusted procedure.

In other words, we can know whether or not the function is a trusted
procedure by asking to the security policy. It is a task of the plugin.

Thanks,

--
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-switcher-function.2.patchtext/x-patch; name=pgsql-switcher-function.2.patchDownload
diff --git a/contrib/dummy_seclabel/dummy_seclabel.c b/contrib/dummy_seclabel/dummy_seclabel.c
index 8bd50a3..557cc0c 100644
--- a/contrib/dummy_seclabel/dummy_seclabel.c
+++ b/contrib/dummy_seclabel/dummy_seclabel.c
@@ -12,14 +12,156 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_proc.h"
 #include "commands/seclabel.h"
 #include "miscadmin.h"
 
 PG_MODULE_MAGIC;
 
+PG_FUNCTION_INFO_V1(dummy_client_label);
+
+Datum dummay_client_label(PG_FUNCTION_ARGS);
+
 /* Entrypoint of the module */
 void _PG_init(void);
 
+static const char *client_label = "unclassified";
+
+static function_call_event_type function_call_event_next = NULL;
+
+typedef struct {
+	Datum	self;
+	Datum	next;
+} private_stack;
+
+static Datum
+dummy_function_call(Oid functionId,
+					FunctionCallEventType event,
+					Datum event_arg)
+{
+	Datum			result = 0;
+	char		   *label;
+	ObjectAddress	object = { .classId = ProcedureRelationId,
+							   .objectId = functionId,
+							   .objectSubId = 0 };
+	switch (event)
+	{
+		case FCET_BE_HOOKED:
+			/*
+			 * If the target function is labeled as "trusted",
+			 * the dummy tries to hook invocation of the function.
+			 */
+			result = BoolGetDatum(false);
+
+			if (function_call_event_next)
+			{
+				result = (*function_call_event_next)(functionId,
+													 event,
+													 event_arg);
+				if (DatumGetBool(result))
+					break;		/* no need to check anymore */
+			}
+			label = GetSecurityLabel(&object, "dummy");
+			if (label && strcmp(label, "trusted") == 0)
+				result = BoolGetDatum(true);
+			break;
+
+		case FCET_PREPARE:
+			/*
+			 * It computes an alternative label during execution
+			 * of the trusted procedure. This computation is not
+			 * necessary to repeat twice or more, so we save it
+			 * on the private opaque data.
+			 */
+			if (function_call_event_next)
+			{
+				FmgrInfo	   *flinfo = (FmgrInfo *)(event_arg);
+				private_stack  *out
+					= MemoryContextAlloc(flinfo->fn_mcxt, sizeof(*out));
+
+				out->next = (*function_call_event_next)(functionId,
+														  event,
+														  event_arg);
+				/*
+				 * XXX - we already checked the function being labeled
+				 *       as "trusted"
+				 */
+				if (!superuser())
+					out->self = PointerGetDatum("secret");
+				else
+					out->self = PointerGetDatum("top secret");
+
+				result = PointerGetDatum(out);
+			}
+			else
+			{
+				if (!superuser())
+					result = PointerGetDatum("secret");
+				else
+					result = PointerGetDatum("top secret");
+			}
+			break;
+
+		case FCET_START:
+			/*
+			 * Switch security label of the client
+			 */
+			if (function_call_event_next)
+			{
+				private_stack  *in = (private_stack *)(event_arg);
+				private_stack  *out = palloc(sizeof(*out));
+
+				out->next = (*function_call_event_next)(functionId,
+														event,
+														in->next);
+				out->self = PointerGetDatum(client_label);
+				client_label = DatumGetPointer(in->self);
+
+				result = PointerGetDatum(out);
+			}
+			else
+			{
+				result = PointerGetDatum(client_label);
+				client_label = DatumGetPointer(event_arg);
+			}
+			break;
+
+		case FCET_END:
+		case FCET_ABORT:
+			/*
+			 * Restore security label of the client
+			 */
+			if (function_call_event_next)
+			{
+				private_stack  *in = (private_stack *)(event_arg);
+
+				(void)(*function_call_event_next)(functionId,
+												  event,
+												  in->next);
+				client_label = DatumGetPointer(in->self);
+			}
+			else
+			{
+				client_label = DatumGetPointer(event_arg);
+			}
+			break;
+
+		default:
+			elog(ERROR, "unexpected event type: %d", (int)event);
+			break;
+	}
+	return result;
+}
+
+Datum
+dummy_client_label(PG_FUNCTION_ARGS)
+{
+	if (!client_label)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text(client_label));
+}
+
 static void
 dummy_object_relabel(const ObjectAddress *object, const char *seclabel)
 {
@@ -28,7 +170,8 @@ dummy_object_relabel(const ObjectAddress *object, const char *seclabel)
 		strcmp(seclabel, "classified") == 0)
 		return;
 
-	if (strcmp(seclabel, "secret") == 0 ||
+	if (strcmp(seclabel, "trusted") == 0 ||
+		strcmp(seclabel, "secret") == 0 ||
 		strcmp(seclabel, "top secret") == 0)
 	{
 		if (!superuser())
@@ -46,4 +189,8 @@ void
 _PG_init(void)
 {
 	register_label_provider("dummy", dummy_object_relabel);
+
+	/* trusted procedure test */
+	function_call_event_next = function_call_event_hook;
+	function_call_event_hook = dummy_function_call;
 }
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index c310416..1d424a9 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -90,6 +90,7 @@ psql -d dbname -f <replaceable>SHAREDIR</>/contrib/<replaceable>module</>.sql
  &dblink;
  &dict-int;
  &dict-xsyn;
+ &dummy_seclabel;
  &earthdistance;
  &fuzzystrmatch;
  &hstore;
diff --git a/doc/src/sgml/dummy_seclabel.sgml b/doc/src/sgml/dummy_seclabel.sgml
new file mode 100644
index 0000000..1d65ff1
--- /dev/null
+++ b/doc/src/sgml/dummy_seclabel.sgml
@@ -0,0 +1,94 @@
+<!-- doc/src/sgml/dummy_seclabel.sgml -->
+
+<sect1 id="dummy_seclabel">
+ <title>dummy_seclabel</title>
+
+ <indexterm zone="dummy_seclabel">
+  <primary>dummy_seclabel</primary>
+ </indexterm>
+
+ <para>
+  The <filename>dummy_seclabel</> module provides a pseudo security label
+  support for regression testing.
+ </para>
+
+ <sect2>
+  <title>Rationale</title>
+
+  <para>
+   <productname>PostgreSQL</> got support <command>SECURITY LABEL</>
+   statement at the version 9.1 or later. It allows us to assign security
+   labels on database objects using plugin modules that are also called
+   external label provider.
+  </para>
+
+  <para>
+   This feature expects plugin modules validate given security labels,
+   because format of the labels completely depends on the security model
+   that plugin tries to provide, so we must install a plugin module to
+   provide security label feature at least.
+  </para>
+
+  <para>
+   However, we need to run regression test for the core features to
+   detect obvious regressions in the future. So, we also needed to ship
+   a dummy security label module independent from the platform.
+  </para>
+ </sect2>
+
+ <sect2>
+  <title>How to Use It</title>
+
+  <para>
+   Here's a simple example of usage:
+  </para>
+
+<programlisting>
+# postgresql.conf
+shared_preload_libraries = 'dummy_label'
+</programlisting>
+
+<programlisting>
+postgres=# CREATE TABLE t (a int, b text);
+CREATE TABLE
+postgres=# SECURITY LABEL ON TABLE t IS 'classified';
+SECURITY LABEL
+</programlisting>
+
+  <para>
+   The <filename>dummy_seclabel</> provide only a few kind of security
+   labels: <literal>unclassified</>, <literal>classified</>,
+   <literal>secret</>, <literal>top secret</> and <literal>trusted</>.
+
+   It does not allow any other strings as security labels.
+  </para>
+  <para>
+   These labels are not used to any valid access controls.
+   So, all we can do is to check whether <command>SECURITY LABEL</>
+   statement works as expected, or not.
+  </para>
+ </sect2>
+
+ <sect2>
+  <title>Limitations</title>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     This module is not intended to provide something useful features,
+     except for regression tests, so we don't recommend to install your
+     systems.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </sect2>
+
+ <sect2>
+  <title>Author</title>
+
+  <para>
+   KaiGai Kohei <email>kaigai@ak.jp.nec.com</email>
+  </para>
+ </sect2>
+
+</sect1>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 9b1de85..ca24638 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -102,6 +102,7 @@
 <!entity dblink          SYSTEM "dblink.sgml">
 <!entity dict-int        SYSTEM "dict-int.sgml">
 <!entity dict-xsyn       SYSTEM "dict-xsyn.sgml">
+<!entity dummy_seclabel  SYSTEM "dummy_seclabel.sgml">
 <!entity earthdistance   SYSTEM "earthdistance.sgml">
 <!entity fuzzystrmatch   SYSTEM "fuzzystrmatch.sgml">
 <!entity hstore          SYSTEM "hstore.sgml">
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index de2e66b..3b64d37 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -3721,6 +3721,10 @@ inline_function(Oid funcid, Oid result_type, List *args,
 	if (pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
 		return NULL;
 
+	/* Check whether plugin want to hook this function, or not */
+	if (IsFunctionCallEventHooked(funcid))
+		return NULL;
+
 	/*
 	 * Make a temporary memory context, so that we don't leak all the stuff
 	 * that parsing might create.
@@ -4153,6 +4157,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	if (pg_proc_aclcheck(func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
 		return NULL;
 
+	/* Check whether plugin want to hook this function, or not */
+	if (IsFunctionCallEventHooked(func_oid))
+		return NULL;
+
 	/*
 	 * OK, let's take a look at the function's pg_proc entry.
 	 */
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 1c9d2c2..e7651cb 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -30,6 +30,10 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
+/*
+ * Hooks for function calls
+ */
+PGDLLIMPORT function_call_event_type	function_call_event_hook = NULL;
 
 /*
  * Declaration for old-style function pointer type.  This is now used only
@@ -230,7 +234,8 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
 	 */
 	if (!ignore_security &&
 		(procedureStruct->prosecdef ||
-		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig)))
+		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) ||
+		 IsFunctionCallEventHooked(functionId)))
 	{
 		finfo->fn_addr = fmgr_security_definer;
 		finfo->fn_stats = TRACK_FUNC_ALL;		/* ie, never track */
@@ -857,6 +862,7 @@ struct fmgr_security_definer_cache
 	FmgrInfo	flinfo;			/* lookup info for target function */
 	Oid			userid;			/* userid to set, or InvalidOid */
 	ArrayType  *proconfig;		/* GUC values to set, or NULL */
+	Datum		private;		/* private usage for hook plugins */
 };
 
 /*
@@ -878,6 +884,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 	Oid			save_userid;
 	int			save_sec_context;
 	volatile int save_nestlevel;
+	Datum		save_datum;
 	PgStat_FunctionCallUsage fcusage;
 
 	if (!fcinfo->flinfo->fn_extra)
@@ -916,6 +923,12 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 
 		ReleaseSysCache(tuple);
 
+		/* Function call event hook */
+		if (function_call_event_hook)
+			fcache->private =
+				(*function_call_event_hook)(fcinfo->flinfo->fn_oid,
+											FCET_PREPARE,
+											PointerGetDatum(&fcache->flinfo));
 		fcinfo->flinfo->fn_extra = fcache;
 	}
 	else
@@ -930,7 +943,7 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 
 	if (OidIsValid(fcache->userid))
 		SetUserIdAndSecContext(fcache->userid,
-							save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+							   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
 	if (fcache->proconfig)
 	{
@@ -940,6 +953,13 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 						GUC_ACTION_SAVE);
 	}
 
+	if (function_call_event_hook)
+		save_datum = (*function_call_event_hook)(fcinfo->flinfo->fn_oid,
+												 FCET_START,
+												 fcache->private);
+	else
+		save_datum = PointerGetDatum(NULL);		/* keep compiler quiet */
+
 	/*
 	 * We don't need to restore GUC or userid settings on error, because the
 	 * ensuing xact or subxact abort will do that.	The PG_TRY block is only
@@ -968,6 +988,9 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 	PG_CATCH();
 	{
 		fcinfo->flinfo = save_flinfo;
+		if (function_call_event_hook)
+			(void)(*function_call_event_hook)(fcinfo->flinfo->fn_oid,
+											  FCET_ABORT, save_datum);
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -978,7 +1001,9 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 		AtEOXact_GUC(true, save_nestlevel);
 	if (OidIsValid(fcache->userid))
 		SetUserIdAndSecContext(save_userid, save_sec_context);
-
+	if (function_call_event_hook)
+		(void)(*function_call_event_hook)(fcinfo->flinfo->fn_oid,
+										  FCET_END, save_datum);
 	return result;
 }
 
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index ca5a5ea..a9a8d31 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -544,6 +544,55 @@ extern void **find_rendezvous_variable(const char *varName);
 extern int AggCheckCallContext(FunctionCallInfo fcinfo,
 					MemoryContext *aggcontext);
 
+/*
+ * function_call_event_hook
+ * ------------------------
+ * This hook allows plugin modules to hook events of function calls.
+ * It enables to switch some of its internal state (such as privilege
+ * of the client) during execution of the hooked function.
+ *
+ * This hook takes three arguments: OID of the function, event typee
+ * and its argument depending on the type.
+ *
+ * FCET_BE_HOOKED is used to ask plugins whether it wants to hook this
+ * function call. This event type has no argument. It shall return
+ * BoolGetDatum(true), if a plugin wants to hook this function
+ *
+ * FCET_PREPARE allows plugins to acquire control on the first invocation
+ * time of the function. This event type delivers a pointer to the FmgrInfo.
+ * It shall return an opaque private data that will be delivered to
+ * FCET_START event.
+ *
+ * FCET_START allows plugins to acquire control just before invocation
+ * of the hooked function for each time. This event type delivers the
+ * opaque private data come from FCET_PREPARE. Then, it can also return
+ * an opaque private to inform something for FCET_END and FCET_ABORT.
+ *
+ * FCET_END and FCET_ABORT allow plugins to acquire control just after
+ * invocation of the hooked function for each time. This event type delivers
+ * an opaque private come from FCET_START.
+ */
+typedef enum FunctionCallEventType
+{
+	FCET_BE_HOOKED,
+	FCET_PREPARE,
+	FCET_START,
+	FCET_END,
+	FCET_ABORT,
+} FunctionCallEventType;
+
+typedef Datum (*function_call_event_type)(Oid functionId,
+										  FunctionCallEventType event,
+										  Datum event_arg);
+extern PGDLLIMPORT function_call_event_type function_call_event_hook;
+
+#define IsFunctionCallEventHooked(funcOid)								\
+	(!function_call_event_hook ?										\
+	 false																\
+	 :																	\
+	 DatumGetBool((*function_call_event_hook)(ObjectIdGetDatum(funcOid), \
+											  FCET_BE_HOOKED, 0))		\
+	)
 
 /*
  * !!! OLD INTERFACE !!!
diff --git a/src/test/regress/input/security_label.source b/src/test/regress/input/security_label.source
index 810a721..8a9646c 100644
--- a/src/test/regress/input/security_label.source
+++ b/src/test/regress/input/security_label.source
@@ -37,6 +37,15 @@ SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified';			-- fail
 -- Load dummy external security provider
 LOAD '@libdir@/dummy_seclabel@DLSUFFIX@';
 
+CREATE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c'
+	AS '@libdir@/dummy_seclabel@DLSUFFIX@', 'dummy_client_label';
+
+CREATE FUNCTION seclabel_regular() RETURNS text LANGUAGE 'sql'
+	AS 'SELECT dummy_client_label()';
+
+CREATE FUNCTION seclabel_trusted() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+
 --
 -- Test of SECURITY LABEL statement with a plugin
 --
@@ -70,7 +79,28 @@ SELECT objtype, objname, provider, label FROM pg_seclabels
 SECURITY LABEL ON LANGUAGE plpgsql IS NULL;						-- OK
 SECURITY LABEL ON SCHEMA public IS NULL;						-- OK
 
+-- test for trusted procedures
+SECURITY LABEL ON FUNCTION seclabel_regular() IS 'unclassified';	-- OK
+SECURITY LABEL ON FUNCTION seclabel_trusted() IS 'trusted';			-- OK
+
+-- should be 'unclassified' and 'top secret'
+SELECT seclabel_regular(), seclabel_trusted();
+
+SET SESSION AUTHORIZATION seclabel_user1;
+
+-- should be 'unclassified' and 'secret'
+SELECT seclabel_regular(), seclabel_trusted();
+
+-- be inlined
+EXPLAIN SELECT * FROM seclabel_regular();
+
+-- be not inlined
+EXPLAIN SELECT * FROM seclabel_trusted();
+
 -- clean up objects
+RESET SESSION AUTHORIZATION;
+DROP FUNCTION seclabel_regular();
+DROP FUNCTION seclabel_trusted();
 DROP FUNCTION seclabel_four();
 DROP DOMAIN seclabel_domain;
 DROP VIEW seclabel_view1;
diff --git a/src/test/regress/output/security_label.source b/src/test/regress/output/security_label.source
index 4bc803d..ec2baff 100644
--- a/src/test/regress/output/security_label.source
+++ b/src/test/regress/output/security_label.source
@@ -30,7 +30,13 @@ ERROR:  no security label providers have been loaded
 SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified';			-- fail
 ERROR:  no security label providers have been loaded
 -- Load dummy external security provider
-LOAD '@abs_builddir@/dummy_seclabel@DLSUFFIX@';
+LOAD '@libdir@/dummy_seclabel@DLSUFFIX@';
+CREATE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c'
+	AS '@libdir@/dummy_seclabel@DLSUFFIX@', 'dummy_client_label';
+CREATE FUNCTION seclabel_regular() RETURNS text LANGUAGE 'sql'
+	AS 'SELECT dummy_client_label()';
+CREATE FUNCTION seclabel_trusted() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
 --
 -- Test of SECURITY LABEL statement with a plugin
 --
@@ -75,7 +81,42 @@ SELECT objtype, objname, provider, label FROM pg_seclabels
 
 SECURITY LABEL ON LANGUAGE plpgsql IS NULL;						-- OK
 SECURITY LABEL ON SCHEMA public IS NULL;						-- OK
+-- test for trusted procedures
+SECURITY LABEL ON FUNCTION seclabel_regular() IS 'unclassified';	-- OK
+SECURITY LABEL ON FUNCTION seclabel_trusted() IS 'trusted';			-- OK
+-- should be 'unclassified' and 'top secret'
+SELECT seclabel_regular(), seclabel_trusted();
+ seclabel_regular | seclabel_trusted 
+------------------+------------------
+ unclassified     | top secret
+(1 row)
+
+SET SESSION AUTHORIZATION seclabel_user1;
+-- should be 'unclassified' and 'secret'
+SELECT seclabel_regular(), seclabel_trusted();
+ seclabel_regular | seclabel_trusted 
+------------------+------------------
+ unclassified     | secret
+(1 row)
+
+-- be inlined
+EXPLAIN SELECT * FROM seclabel_regular();
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Function Scan on dummy_client_label seclabel_regular  (cost=0.00..0.01 rows=1 width=32)
+(1 row)
+
+-- be not inlined
+EXPLAIN SELECT * FROM seclabel_trusted();
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Function Scan on seclabel_trusted  (cost=0.25..0.26 rows=1 width=32)
+(1 row)
+
 -- clean up objects
+RESET SESSION AUTHORIZATION;
+DROP FUNCTION seclabel_regular();
+DROP FUNCTION seclabel_trusted();
 DROP FUNCTION seclabel_four();
 DROP DOMAIN seclabel_domain;
 DROP VIEW seclabel_view1;
#5Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#4)
Re: Label switcher function

2010/11/17 KaiGai Kohei <kaigai@ak.jp.nec.com>:

I also fixed up regression test, dummy_seclabel module and its
documentation as Robert pointed out in another topic.

I have committed the documentation portion of this patch with some
editing. I also fixed the markup, which was broken, because you used
_ in several places where it isn't valid.

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

#6Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#4)
Re: Label switcher function

2010/11/17 KaiGai Kohei <kaigai@ak.jp.nec.com>:

I revised my patch as I attached.

The hook function is modified and consolidated as follows:

 typedef enum FunctionCallEventType
 {
    FCET_BE_HOOKED,
    FCET_PREPARE,
    FCET_START,
    FCET_END,
    FCET_ABORT,
 } FunctionCallEventType;

 typedef Datum (*function_call_event_type)(Oid functionId,
                                           FunctionCallEventType event,
                                           Datum event_arg);
 extern PGDLLIMPORT function_call_event_type function_call_event_hook;

Unlike the subject of this e-mail, now it does not focus on only switching
security labels during execution of a certain functions.
For example, we may use this hook to track certain functions for security
auditing, performance tuning, and others.

In the case of SE-PgSQL, it shall return BoolGetDatum(true), if the target
function is configured as a trusted procedure, then, this invocation will
be hooked by fmgr_security_definer. In the first call, it shall compute
the security context to be assigned during execution on FCET_PREPARE event.
Then, it switches to the computed label on the FCET_START event, and
restore it on the FCET_END or ECET_ABORT event.

This seems like it's a lot simpler than before, which is good. It
looks to me as though there should really be two separate hooks,
though, one for what is now FCET_BE_HOOKED and one for everything
else. For FCET_BE_HOOKED, you want a function that takes an Oid and
returns a bool. For the other event types, the functionId and event
arguments are OK, but I think you should forget about the save_datum
stuff and just always pass fcache->flinfo and &fcache->private. The
plugin can get the effect of save_datum by passing around whatever
state it needs to hold on to using fcache->private. So:

bool (*needs_function_call_hook)(Oid fn_oid);
void (*function_call_hook)(Oid fn_oid, FunctionCallEventType event,
FmgrInfo flinfo, Datum *private);

Another general comment is that you've not done a very complete job
updating the comments; there are several of them in fmgr.c that are no
longer accurate. Also, please zap the unnecessary whitespace changes.

Thanks,

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

#7KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#6)
Re: Label switcher function

(2010/11/18 11:30), Robert Haas wrote:

2010/11/17 KaiGai Kohei<kaigai@ak.jp.nec.com>:

I revised my patch as I attached.

The hook function is modified and consolidated as follows:

typedef enum FunctionCallEventType
{
FCET_BE_HOOKED,
FCET_PREPARE,
FCET_START,
FCET_END,
FCET_ABORT,
} FunctionCallEventType;

typedef Datum (*function_call_event_type)(Oid functionId,
FunctionCallEventType event,
Datum event_arg);
extern PGDLLIMPORT function_call_event_type function_call_event_hook;

Unlike the subject of this e-mail, now it does not focus on only switching
security labels during execution of a certain functions.
For example, we may use this hook to track certain functions for security
auditing, performance tuning, and others.

In the case of SE-PgSQL, it shall return BoolGetDatum(true), if the target
function is configured as a trusted procedure, then, this invocation will
be hooked by fmgr_security_definer. In the first call, it shall compute
the security context to be assigned during execution on FCET_PREPARE event.
Then, it switches to the computed label on the FCET_START event, and
restore it on the FCET_END or ECET_ABORT event.

This seems like it's a lot simpler than before, which is good. It
looks to me as though there should really be two separate hooks,
though, one for what is now FCET_BE_HOOKED and one for everything
else. For FCET_BE_HOOKED, you want a function that takes an Oid and
returns a bool. For the other event types, the functionId and event
arguments are OK, but I think you should forget about the save_datum
stuff and just always pass fcache->flinfo and&fcache->private. The
plugin can get the effect of save_datum by passing around whatever
state it needs to hold on to using fcache->private. So:

bool (*needs_function_call_hook)(Oid fn_oid);
void (*function_call_hook)(Oid fn_oid, FunctionCallEventType event,
FmgrInfo flinfo, Datum *private);

It seems to me a good idea. The characteristic of FCET_BE_HOOKED event
type was a bit different from other three event types.
Please wait for about a week to revise my patch.

Another general comment is that you've not done a very complete job
updating the comments; there are several of them in fmgr.c that are no
longer accurate. Also, please zap the unnecessary whitespace changes.

Indeed, the comment at middle of the fmgr_info_cxt_security() and just
above definition of the fmgr_security_definer() are not correct.
Did you notice anything else?

Thanks,
--
KaiGai Kohei <kaigai@ak.jp.nec.com>

#8Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#7)
Re: Label switcher function

2010/11/19 KaiGai Kohei <kaigai@ak.jp.nec.com>:

Indeed, the comment at middle of the fmgr_info_cxt_security() and just
above definition of the fmgr_security_definer() are not correct.
Did you notice anything else?

I think I noticed a couple of places, but I didn't write down exactly
which ones. Sorry....

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

#9KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#6)
1 attachment(s)
Re: Label switcher function

The attached patch is a revised one.

It provides two hooks; the one informs core PG whether the supplied
function needs to be hooked, or not. the other is an actual hook on
prepare, start, end and abort of function invocations.

typedef bool (*needs_function_call_type)(Oid fn_oid);

typedef void (*function_call_type)(FunctionCallEventType event,
FmgrInfo *flinfo, Datum *private);

The hook prototype was a bit modified since the suggestion from
Robert. Because FmgrInfo structure contain OID of the function,
it might be redundant to deliver OID of the function individually.

Rest of parts are revised according to the comment.

I also fixed up source code comments which might become incorrect.

Thanks,

(2010/11/18 11:30), Robert Haas wrote:

2010/11/17 KaiGai Kohei<kaigai@ak.jp.nec.com>:

I revised my patch as I attached.

The hook function is modified and consolidated as follows:

typedef enum FunctionCallEventType
{
FCET_BE_HOOKED,
FCET_PREPARE,
FCET_START,
FCET_END,
FCET_ABORT,
} FunctionCallEventType;

typedef Datum (*function_call_event_type)(Oid functionId,
FunctionCallEventType event,
Datum event_arg);
extern PGDLLIMPORT function_call_event_type function_call_event_hook;

Unlike the subject of this e-mail, now it does not focus on only switching
security labels during execution of a certain functions.
For example, we may use this hook to track certain functions for security
auditing, performance tuning, and others.

In the case of SE-PgSQL, it shall return BoolGetDatum(true), if the target
function is configured as a trusted procedure, then, this invocation will
be hooked by fmgr_security_definer. In the first call, it shall compute
the security context to be assigned during execution on FCET_PREPARE event.
Then, it switches to the computed label on the FCET_START event, and
restore it on the FCET_END or ECET_ABORT event.

This seems like it's a lot simpler than before, which is good. It
looks to me as though there should really be two separate hooks,
though, one for what is now FCET_BE_HOOKED and one for everything
else. For FCET_BE_HOOKED, you want a function that takes an Oid and
returns a bool. For the other event types, the functionId and event
arguments are OK, but I think you should forget about the save_datum
stuff and just always pass fcache->flinfo and&fcache->private. The
plugin can get the effect of save_datum by passing around whatever
state it needs to hold on to using fcache->private. So:

bool (*needs_function_call_hook)(Oid fn_oid);
void (*function_call_hook)(Oid fn_oid, FunctionCallEventType event,
FmgrInfo flinfo, Datum *private);

Another general comment is that you've not done a very complete job
updating the comments; there are several of them in fmgr.c that are no
longer accurate. Also, please zap the unnecessary whitespace changes.

Thanks,

--
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-switcher-function.3.patchtext/x-patch; name=pgsql-switcher-function.3.patchDownload
 contrib/dummy_seclabel/dummy_seclabel.c       |  102 ++++++++++++++++++++++++-
 src/backend/optimizer/util/clauses.c          |    8 ++
 src/backend/utils/fmgr/fmgr.c                 |   44 ++++++++---
 src/include/fmgr.h                            |   55 +++++++++++++
 src/test/regress/input/security_label.source  |   28 +++++++
 src/test/regress/output/security_label.source |   43 ++++++++++-
 6 files changed, 267 insertions(+), 13 deletions(-)

diff --git a/contrib/dummy_seclabel/dummy_seclabel.c b/contrib/dummy_seclabel/dummy_seclabel.c
index 8bd50a3..7acb512 100644
--- a/contrib/dummy_seclabel/dummy_seclabel.c
+++ b/contrib/dummy_seclabel/dummy_seclabel.c
@@ -12,14 +12,105 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_proc.h"
 #include "commands/seclabel.h"
 #include "miscadmin.h"
 
 PG_MODULE_MAGIC;
 
+PG_FUNCTION_INFO_V1(dummy_client_label);
+
 /* Entrypoint of the module */
 void _PG_init(void);
 
+/* SQL functions */
+Datum dummay_client_label(PG_FUNCTION_ARGS);
+
+static const char *client_label = "unclassified";
+
+typedef struct {
+	const char *old_label;
+	const char *new_label;
+	Datum		next_private;
+} dummy_stack;
+
+static needs_function_call_type	needs_function_call_next = NULL;
+static function_call_type		function_call_next = NULL;
+
+static bool
+dummy_needs_function_call(Oid fn_oid)
+{
+	char		   *label;
+	bool			result = false;
+	ObjectAddress	object = { .classId = ProcedureRelationId,
+							   .objectId = fn_oid,
+							   .objectSubId = 0 };
+
+	if (needs_function_call_next &&
+		(*needs_function_call_next)(fn_oid))
+		return true;
+
+	label = GetSecurityLabel(&object, "dummy");
+	if (label && strcmp(label, "trusted") == 0)
+		result = true;
+
+	if (label)
+		pfree(label);
+
+	return result;
+}
+
+static void
+dummy_function_call(FunctionCallEventType event,
+					FmgrInfo *flinfo, Datum *private)
+{
+	dummy_stack	   *stack;
+
+	switch (event)
+	{
+		case FCET_PREPARE:
+			stack = MemoryContextAlloc(flinfo->fn_mcxt,
+									   sizeof(dummy_stack));
+			stack->old_label = NULL;
+			stack->new_label = (!superuser() ? "secret" : "top secret");
+			stack->next_private = 0;
+
+			if (function_call_next)
+				(*function_call_next)(event, flinfo, &stack->next_private);
+
+			*private = PointerGetDatum(stack);
+			break;
+
+		case FCET_START:
+			stack = (dummy_stack *)DatumGetPointer(*private);
+
+			stack->old_label = client_label;
+			client_label = stack->new_label;
+			break;
+
+		case FCET_END:
+		case FCET_ABORT:
+			stack = (dummy_stack *)DatumGetPointer(*private);
+
+			client_label = stack->old_label;
+			stack->old_label = NULL;
+			break;
+
+		default:
+			elog(ERROR, "unexpected event type: %d", (int)event);
+			break;
+	}
+}
+
+Datum
+dummy_client_label(PG_FUNCTION_ARGS)
+{
+	if (!client_label)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text(client_label));
+}
+
 static void
 dummy_object_relabel(const ObjectAddress *object, const char *seclabel)
 {
@@ -28,7 +119,8 @@ dummy_object_relabel(const ObjectAddress *object, const char *seclabel)
 		strcmp(seclabel, "classified") == 0)
 		return;
 
-	if (strcmp(seclabel, "secret") == 0 ||
+	if (strcmp(seclabel, "trusted") == 0 ||
+		strcmp(seclabel, "secret") == 0 ||
 		strcmp(seclabel, "top secret") == 0)
 	{
 		if (!superuser())
@@ -46,4 +138,12 @@ void
 _PG_init(void)
 {
 	register_label_provider("dummy", dummy_object_relabel);
+
+	/* needs_function_call_hook */
+	needs_function_call_next = needs_function_call_hook;
+	needs_function_call_hook = dummy_needs_function_call;
+
+	/* function_call_hook */
+	function_call_next = function_call_hook;
+	function_call_hook = dummy_function_call;
 }
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index de2e66b..c31c36d 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -3721,6 +3721,10 @@ inline_function(Oid funcid, Oid result_type, List *args,
 	if (pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
 		return NULL;
 
+	/* Check whether the function shall be hooked by plugins */
+	if (FunctionCallIsHooked(funcid))
+		return NULL;
+
 	/*
 	 * Make a temporary memory context, so that we don't leak all the stuff
 	 * that parsing might create.
@@ -4153,6 +4157,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	if (pg_proc_aclcheck(func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
 		return NULL;
 
+	/* Check whether the function shall be hooked by plugins */
+	if (FunctionCallIsHooked(func_oid))
+		return NULL;
+
 	/*
 	 * OK, let's take a look at the function's pg_proc entry.
 	 */
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 1c9d2c2..c11e075 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -30,6 +30,11 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
+/*
+ * Hooks for function calls
+ */
+PGDLLIMPORT needs_function_call_type needs_function_call_hook = NULL;
+PGDLLIMPORT function_call_type function_call_hook = NULL;
 
 /*
  * Declaration for old-style function pointer type.  This is now used only
@@ -216,7 +221,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
 	finfo->fn_retset = procedureStruct->proretset;
 
 	/*
-	 * If it has prosecdef set, or non-null proconfig, use
+	 * If it has prosecdef set, non-null proconfig, hook by plugins use
 	 * fmgr_security_definer call handler --- unless we are being called again
 	 * by fmgr_security_definer.
 	 *
@@ -230,7 +235,8 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
 	 */
 	if (!ignore_security &&
 		(procedureStruct->prosecdef ||
-		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig)))
+		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) ||
+		 FunctionCallIsHooked(functionId)))
 	{
 		finfo->fn_addr = fmgr_security_definer;
 		finfo->fn_stats = TRACK_FUNC_ALL;		/* ie, never track */
@@ -857,17 +863,18 @@ struct fmgr_security_definer_cache
 	FmgrInfo	flinfo;			/* lookup info for target function */
 	Oid			userid;			/* userid to set, or InvalidOid */
 	ArrayType  *proconfig;		/* GUC values to set, or NULL */
+	Datum		private;		/* private usage for plugin modules */
 };
 
 /*
- * Function handler for security-definer/proconfig functions.  We extract the
- * OID of the actual function and do a fmgr lookup again.  Then we fetch the
- * pg_proc row and copy the owner ID and proconfig fields.	(All this info
- * is cached for the duration of the current query.)  To execute a call,
- * we temporarily replace the flinfo with the cached/looked-up one, while
- * keeping the outer fcinfo (which contains all the actual arguments, etc.)
- * intact.	This is not re-entrant, but then the fcinfo itself can't be used
- * re-entrantly anyway.
+ * Function handler for security-definer/proconfig/plugin-hooked functions.
+ * We extract the OID of the actual function and do a fmgr lookup again.
+ * Then we fetch the pg_proc row and copy the owner ID and proconfig fields.
+ * (All this info is cached for the duration of the current query.)
+ * To execute a call, we temporarily replace the flinfo with the cached
+ * and looked-up one, while keeping the outer fcinfo (which contains all
+ * the actual arguments, etc.) intact.	This is not re-entrant, but then
+ * the fcinfo itself can't be used re-entrantly anyway.
  */
 static Datum
 fmgr_security_definer(PG_FUNCTION_ARGS)
@@ -916,6 +923,11 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 
 		ReleaseSysCache(tuple);
 
+		/* function call event hook */
+		if (function_call_hook)
+			(*function_call_hook)(FCET_PREPARE,
+								  &fcache->flinfo, &fcache->private);
+
 		fcinfo->flinfo->fn_extra = fcache;
 	}
 	else
@@ -940,6 +952,11 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 						GUC_ACTION_SAVE);
 	}
 
+	/* function call event hook */
+	if (function_call_hook)
+		(*function_call_hook)(FCET_START,
+							  &fcache->flinfo, &fcache->private);
+
 	/*
 	 * We don't need to restore GUC or userid settings on error, because the
 	 * ensuing xact or subxact abort will do that.	The PG_TRY block is only
@@ -968,6 +985,9 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 	PG_CATCH();
 	{
 		fcinfo->flinfo = save_flinfo;
+		if (function_call_hook)
+			(*function_call_hook)(FCET_ABORT,
+								  &fcache->flinfo, &fcache->private);
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -978,7 +998,9 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 		AtEOXact_GUC(true, save_nestlevel);
 	if (OidIsValid(fcache->userid))
 		SetUserIdAndSecContext(save_userid, save_sec_context);
-
+	if (function_call_hook)
+		(*function_call_hook)(FCET_END,
+							  &fcache->flinfo, &fcache->private);
 	return result;
 }
 
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index ca5a5ea..9cbaa80 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -544,6 +544,61 @@ extern void **find_rendezvous_variable(const char *varName);
 extern int AggCheckCallContext(FunctionCallInfo fcinfo,
 					MemoryContext *aggcontext);
 
+/*
+ * function_call_hook
+ * ------------------
+ * This hook allows plugin modules to hook events of function calls.
+ * It enables to switch some of its internal state (such as privilege
+ * of the client) during execution of the hooked function.
+ *
+ * The plugin module has to set up two hooks for this feature at least.
+ * The one is 'needs_function_call_hook' which enables to inform whether
+ * the plugin module wants to hook the supplied function, or not.
+ * It returns 'true', if the supplied function shall be hooked. Elsewhere,
+ * 'false' shall be returned.
+ *
+ * The other hook is 'function_call_hook' that enables plugin modules
+ * to acquire control when the supplied function is prepared, started,
+ * ended and aborted.
+ * It takes four arguments: OID of the function, event type (defined
+ * as FunctionCallEventType), FmgrInfo and pointer to the private
+ * data field.
+ *
+ * FCET_PREPARE event type allows plugins to acquire control on the
+ * first invocation time of the function only once. We intend to use
+ * this callback to set up private data structure for later uses.
+ * If you want to call palloc(), note that CurrentMemoryContext is not
+ * available during whole of execution, so use FmgrInfo->mcxt instead.
+ *
+ * FCET_START event type allows plugins to acquire control just before
+ * invocation of the hooked function for each time. If necessary, the
+ * plugin module can switch its internal state.
+ *
+ * FCET_END and FCET_ABORT allow plugins to acquire control just after
+ * invocation of the hooked function for each time. If necessary, the
+ * plugin module can restore its internal state.
+ */
+typedef enum FunctionCallEventType
+{
+	FCET_PREPARE,
+	FCET_START,
+	FCET_END,
+	FCET_ABORT
+} FunctionCallEventType;
+
+typedef bool (*needs_function_call_type)(Oid fn_oid);
+
+typedef void (*function_call_type)(FunctionCallEventType event,
+								   FmgrInfo *flinfo, Datum *private);
+
+extern PGDLLIMPORT needs_function_call_type needs_function_call_hook;
+extern PGDLLIMPORT function_call_type function_call_hook;
+
+#define FunctionCallIsHooked(fn_oid)			\
+	(!needs_function_call_hook					\
+	 ? false									\
+	 : (*needs_function_call_hook)(fn_oid)		\
+	)
 
 /*
  * !!! OLD INTERFACE !!!
diff --git a/src/test/regress/input/security_label.source b/src/test/regress/input/security_label.source
index 810a721..9fb2a2d 100644
--- a/src/test/regress/input/security_label.source
+++ b/src/test/regress/input/security_label.source
@@ -37,6 +37,15 @@ SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified';			-- fail
 -- Load dummy external security provider
 LOAD '@libdir@/dummy_seclabel@DLSUFFIX@';
 
+CREATE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c'
+    AS '@libdir@/dummy_seclabel@DLSUFFIX@', 'dummy_client_label';
+
+CREATE FUNCTION seclabel_regular() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+
+CREATE FUNCTION seclabel_trusted() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+
 --
 -- Test of SECURITY LABEL statement with a plugin
 --
@@ -70,7 +79,26 @@ SELECT objtype, objname, provider, label FROM pg_seclabels
 SECURITY LABEL ON LANGUAGE plpgsql IS NULL;						-- OK
 SECURITY LABEL ON SCHEMA public IS NULL;						-- OK
 
+-- test for trusted procedures
+SECURITY LABEL ON FUNCTION seclabel_regular() IS 'unclassified';	-- OK
+SECURITY LABEL ON FUNCTION seclabel_trusted() IS 'trusted';			-- OK
+
+-- should be 'unclassified' and 'top secret'
+SELECT seclabel_regular(), seclabel_trusted();
+
+-- should be 'unclassified' and 'secret'
+SET SESSION AUTHORIZATION seclabel_user1;
+SELECT seclabel_regular(), seclabel_trusted();
+RESET SESSION AUTHORIZATION;
+
+-- hooked functions shall not be inlined
+EXPLAIN SELECT * FROM seclabel_regular();		-- shall be inlined
+EXPLAIN SELECT * FROM seclabel_trusted();		-- shall not be inlined
+
 -- clean up objects
+RESET SESSION AUTHORIZATION;
+DROP FUNCTION seclabel_regular();
+DROP FUNCTION seclabel_trusted();
 DROP FUNCTION seclabel_four();
 DROP DOMAIN seclabel_domain;
 DROP VIEW seclabel_view1;
diff --git a/src/test/regress/output/security_label.source b/src/test/regress/output/security_label.source
index 4bc803d..fb3809e 100644
--- a/src/test/regress/output/security_label.source
+++ b/src/test/regress/output/security_label.source
@@ -30,7 +30,13 @@ ERROR:  no security label providers have been loaded
 SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified';			-- fail
 ERROR:  no security label providers have been loaded
 -- Load dummy external security provider
-LOAD '@abs_builddir@/dummy_seclabel@DLSUFFIX@';
+LOAD '@libdir@/dummy_seclabel@DLSUFFIX@';
+CREATE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c'
+    AS '@libdir@/dummy_seclabel@DLSUFFIX@', 'dummy_client_label';
+CREATE FUNCTION seclabel_regular() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+CREATE FUNCTION seclabel_trusted() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
 --
 -- Test of SECURITY LABEL statement with a plugin
 --
@@ -75,7 +81,42 @@ SELECT objtype, objname, provider, label FROM pg_seclabels
 
 SECURITY LABEL ON LANGUAGE plpgsql IS NULL;						-- OK
 SECURITY LABEL ON SCHEMA public IS NULL;						-- OK
+-- test for trusted procedures
+SECURITY LABEL ON FUNCTION seclabel_regular() IS 'unclassified';	-- OK
+SECURITY LABEL ON FUNCTION seclabel_trusted() IS 'trusted';			-- OK
+-- should be 'unclassified' and 'top secret'
+SELECT seclabel_regular(), seclabel_trusted();
+ seclabel_regular | seclabel_trusted 
+------------------+------------------
+ unclassified     | top secret
+(1 row)
+
+-- should be 'unclassified' and 'secret'
+SET SESSION AUTHORIZATION seclabel_user1;
+SELECT seclabel_regular(), seclabel_trusted();
+ seclabel_regular | seclabel_trusted 
+------------------+------------------
+ unclassified     | secret
+(1 row)
+
+RESET SESSION AUTHORIZATION;
+-- hooked functions shall not be inlined
+EXPLAIN SELECT * FROM seclabel_regular();		-- shall be inlined
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Function Scan on dummy_client_label seclabel_regular  (cost=0.00..0.01 rows=1 width=32)
+(1 row)
+
+EXPLAIN SELECT * FROM seclabel_trusted();		-- shall not be inlined
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Function Scan on seclabel_trusted  (cost=0.25..0.26 rows=1 width=32)
+(1 row)
+
 -- clean up objects
+RESET SESSION AUTHORIZATION;
+DROP FUNCTION seclabel_regular();
+DROP FUNCTION seclabel_trusted();
 DROP FUNCTION seclabel_four();
 DROP DOMAIN seclabel_domain;
 DROP VIEW seclabel_view1;
#10Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#9)
Re: Label switcher function

2010/11/25 KaiGai Kohei <kaigai@ak.jp.nec.com>:

The attached patch is a revised one.

It provides two hooks; the one informs core PG whether the supplied
function needs to be hooked, or not. the other is an actual hook on
prepare, start, end and abort of function invocations.

 typedef bool (*needs_function_call_type)(Oid fn_oid);

 typedef void (*function_call_type)(FunctionCallEventType event,
                                    FmgrInfo *flinfo, Datum *private);

The hook prototype was a bit modified since the suggestion from
Robert. Because FmgrInfo structure contain OID of the function,
it might be redundant to deliver OID of the function individually.

Rest of parts are revised according to the comment.

I also fixed up source code comments which might become incorrect.

FCET_PREPARE looks completely unnecessary to me. Any necessary
one-time work can easily be done at FCET_START time, assuming that the
private-data field is initialized to (Datum) 0.

I'm fairly certain that the following is not portable:

+       ObjectAddress   object = { .classId = ProcedureRelationId,
+                                                          .objectId = fn_oid,
+                                                          .objectSubId = 0 };

I'd suggest renaming needs_function_call_type and function_call_type
to needs_fmgr_hook_type and fmgr_hook_type.

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

#11KaiGai Kohei
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#10)
1 attachment(s)
Re: Label switcher function

Thanks for your reviewing.

The attached patch is a revised version.
I don't have any objections to your comments.

(2010/12/07 4:38), Robert Haas wrote:

2010/11/25 KaiGai Kohei<kaigai@ak.jp.nec.com>:

The attached patch is a revised one.

It provides two hooks; the one informs core PG whether the supplied
function needs to be hooked, or not. the other is an actual hook on
prepare, start, end and abort of function invocations.

typedef bool (*needs_function_call_type)(Oid fn_oid);

typedef void (*function_call_type)(FunctionCallEventType event,
FmgrInfo *flinfo, Datum *private);

The hook prototype was a bit modified since the suggestion from
Robert. Because FmgrInfo structure contain OID of the function,
it might be redundant to deliver OID of the function individually.

Rest of parts are revised according to the comment.

I also fixed up source code comments which might become incorrect.

FCET_PREPARE looks completely unnecessary to me. Any necessary
one-time work can easily be done at FCET_START time, assuming that the
private-data field is initialized to (Datum) 0.

It seems to me a reasonable assumption.
I revised the code, and modified source code comments to ensure
the private shall be initialized to (Datum) 0.

I'm fairly certain that the following is not portable:

+       ObjectAddress   object = { .classId = ProcedureRelationId,
+                                                          .objectId = fn_oid,
+                                                          .objectSubId = 0 };

Fixed.

I'd suggest renaming needs_function_call_type and function_call_type
to needs_fmgr_hook_type and fmgr_hook_type.

I also think the suggested names are better than before.

According to the renaming, FunctionCallEventType was also renamed to
FmgrHookEventType. Perhaps, it is a reasonable change.

Thanks,
--
KaiGai Kohei <kaigai@ak.jp.nec.com>

Attachments:

pgsql-switcher-function.4.patchtext/x-patch; name=pgsql-switcher-function.4.patchDownload
 contrib/dummy_seclabel/dummy_seclabel.c       |  107 ++++++++++++++++++++++++-
 src/backend/optimizer/util/clauses.c          |    8 ++
 src/backend/utils/fmgr/fmgr.c                 |   35 ++++++---
 src/include/fmgr.h                            |   46 +++++++++++
 src/test/regress/input/security_label.source  |   28 +++++++
 src/test/regress/output/security_label.source |   43 ++++++++++-
 6 files changed, 255 insertions(+), 12 deletions(-)

diff --git a/contrib/dummy_seclabel/dummy_seclabel.c b/contrib/dummy_seclabel/dummy_seclabel.c
index 8bd50a3..237419a 100644
--- a/contrib/dummy_seclabel/dummy_seclabel.c
+++ b/contrib/dummy_seclabel/dummy_seclabel.c
@@ -12,14 +12,110 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_proc.h"
 #include "commands/seclabel.h"
 #include "miscadmin.h"
 
 PG_MODULE_MAGIC;
 
+PG_FUNCTION_INFO_V1(dummy_client_label);
+
 /* Entrypoint of the module */
 void _PG_init(void);
 
+/* SQL functions */
+Datum dummay_client_label(PG_FUNCTION_ARGS);
+
+static const char *client_label = "unclassified";
+
+typedef struct {
+	const char *old_label;
+	const char *new_label;
+	Datum		next_private;
+} dummy_stack;
+
+static needs_fmgr_hook_type	needs_fmgr_next = NULL;
+static fmgr_hook_type		fmgr_next = NULL;
+
+static bool
+dummy_needs_fmgr_hook(Oid fn_oid)
+{
+	char		   *label;
+	bool			result = false;
+	ObjectAddress	object;
+
+	if (needs_fmgr_next && (*needs_fmgr_next)(fn_oid))
+		return true;
+
+	object.classId = ProcedureRelationId;
+	object.objectId = fn_oid;
+	object.objectSubId = 0;
+	label = GetSecurityLabel(&object, "dummy");
+	if (label && strcmp(label, "trusted") == 0)
+		result = true;
+
+	if (label)
+		pfree(label);
+
+	return result;
+}
+
+static void
+dummy_fmgr_hook(FmgrHookEventType event,
+				FmgrInfo *flinfo, Datum *private)
+{
+	dummy_stack	   *stack;
+
+	switch (event)
+	{
+		case FHET_START:
+			/*
+			 * In the first call, we allocate a pseudo stack for private
+			 * datum of the secondary plugin module.
+			 */
+			if (*private == 0)
+			{
+				stack = MemoryContextAlloc(flinfo->fn_mcxt,
+										   sizeof(dummy_stack));
+				stack->old_label = NULL;
+				stack->new_label = (!superuser() ? "secret" : "top secret");
+				stack->next_private = 0;
+
+				if (fmgr_next)
+					(*fmgr_next)(event, flinfo, &stack->next_private);
+
+				*private = PointerGetDatum(stack);
+			}
+			else
+				stack = (dummy_stack *)DatumGetPointer(*private);
+
+			stack->old_label = client_label;
+			client_label = stack->new_label;
+			break;
+
+		case FHET_END:
+		case FHET_ABORT:
+			stack = (dummy_stack *)DatumGetPointer(*private);
+
+			client_label = stack->old_label;
+			stack->old_label = NULL;
+			break;
+
+		default:
+			elog(ERROR, "unexpected event type: %d", (int)event);
+			break;
+	}
+}
+
+Datum
+dummy_client_label(PG_FUNCTION_ARGS)
+{
+	if (!client_label)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text(client_label));
+}
+
 static void
 dummy_object_relabel(const ObjectAddress *object, const char *seclabel)
 {
@@ -28,7 +124,8 @@ dummy_object_relabel(const ObjectAddress *object, const char *seclabel)
 		strcmp(seclabel, "classified") == 0)
 		return;
 
-	if (strcmp(seclabel, "secret") == 0 ||
+	if (strcmp(seclabel, "trusted") == 0 ||
+		strcmp(seclabel, "secret") == 0 ||
 		strcmp(seclabel, "top secret") == 0)
 	{
 		if (!superuser())
@@ -46,4 +143,12 @@ void
 _PG_init(void)
 {
 	register_label_provider("dummy", dummy_object_relabel);
+
+	/* needs_function_call_hook */
+	needs_fmgr_next = needs_fmgr_hook;
+	needs_fmgr_hook = dummy_needs_fmgr_hook;
+
+	/* function_call_hook */
+	fmgr_next = fmgr_hook;
+	fmgr_hook = dummy_fmgr_hook;
 }
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 80dfaad..8df5331 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -3726,6 +3726,10 @@ inline_function(Oid funcid, Oid result_type, List *args,
 	if (pg_proc_aclcheck(funcid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
 		return NULL;
 
+	/* Check whether the function shall be hooked by plugins */
+	if (FmgrHookIsNeeded(funcid))
+		return NULL;
+
 	/*
 	 * Make a temporary memory context, so that we don't leak all the stuff
 	 * that parsing might create.
@@ -4158,6 +4162,10 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	if (pg_proc_aclcheck(func_oid, GetUserId(), ACL_EXECUTE) != ACLCHECK_OK)
 		return NULL;
 
+	/* Check whether the function shall be hooked by plugins */
+	if (FmgrHookIsNeeded(func_oid))
+		return NULL;
+
 	/*
 	 * OK, let's take a look at the function's pg_proc entry.
 	 */
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 1c9d2c2..ad459dd 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -30,6 +30,11 @@
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 
+/*
+ * Hooks for function calls
+ */
+PGDLLIMPORT needs_fmgr_hook_type needs_fmgr_hook = NULL;
+PGDLLIMPORT fmgr_hook_type       fmgr_hook = NULL;
 
 /*
  * Declaration for old-style function pointer type.  This is now used only
@@ -216,7 +221,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
 	finfo->fn_retset = procedureStruct->proretset;
 
 	/*
-	 * If it has prosecdef set, or non-null proconfig, use
+	 * If it has prosecdef set, non-null proconfig, hook by plugins use
 	 * fmgr_security_definer call handler --- unless we are being called again
 	 * by fmgr_security_definer.
 	 *
@@ -230,7 +235,8 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
 	 */
 	if (!ignore_security &&
 		(procedureStruct->prosecdef ||
-		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig)))
+		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) ||
+		 FmgrHookIsNeeded(functionId)))
 	{
 		finfo->fn_addr = fmgr_security_definer;
 		finfo->fn_stats = TRACK_FUNC_ALL;		/* ie, never track */
@@ -857,17 +863,18 @@ struct fmgr_security_definer_cache
 	FmgrInfo	flinfo;			/* lookup info for target function */
 	Oid			userid;			/* userid to set, or InvalidOid */
 	ArrayType  *proconfig;		/* GUC values to set, or NULL */
+	Datum		private;		/* private usage for plugin modules */
 };
 
 /*
- * Function handler for security-definer/proconfig functions.  We extract the
- * OID of the actual function and do a fmgr lookup again.  Then we fetch the
- * pg_proc row and copy the owner ID and proconfig fields.	(All this info
- * is cached for the duration of the current query.)  To execute a call,
- * we temporarily replace the flinfo with the cached/looked-up one, while
- * keeping the outer fcinfo (which contains all the actual arguments, etc.)
- * intact.	This is not re-entrant, but then the fcinfo itself can't be used
- * re-entrantly anyway.
+ * Function handler for security-definer/proconfig/plugin-hooked functions.
+ * We extract the OID of the actual function and do a fmgr lookup again.
+ * Then we fetch the pg_proc row and copy the owner ID and proconfig fields.
+ * (All this info is cached for the duration of the current query.)
+ * To execute a call, we temporarily replace the flinfo with the cached
+ * and looked-up one, while keeping the outer fcinfo (which contains all
+ * the actual arguments, etc.) intact.	This is not re-entrant, but then
+ * the fcinfo itself can't be used re-entrantly anyway.
  */
 static Datum
 fmgr_security_definer(PG_FUNCTION_ARGS)
@@ -940,6 +947,10 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 						GUC_ACTION_SAVE);
 	}
 
+	/* function manager hook */
+	if (fmgr_hook)
+		(*fmgr_hook)(FHET_START, &fcache->flinfo, &fcache->private);
+
 	/*
 	 * We don't need to restore GUC or userid settings on error, because the
 	 * ensuing xact or subxact abort will do that.	The PG_TRY block is only
@@ -968,6 +979,8 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 	PG_CATCH();
 	{
 		fcinfo->flinfo = save_flinfo;
+		if (fmgr_hook)
+			(*fmgr_hook)(FHET_ABORT, &fcache->flinfo, &fcache->private);
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
@@ -978,6 +991,8 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
 		AtEOXact_GUC(true, save_nestlevel);
 	if (OidIsValid(fcache->userid))
 		SetUserIdAndSecContext(save_userid, save_sec_context);
+	if (fmgr_hook)
+		(*fmgr_hook)(FHET_END, &fcache->flinfo, &fcache->private);
 
 	return result;
 }
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index ca5a5ea..e42ff90 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -544,6 +544,52 @@ extern void **find_rendezvous_variable(const char *varName);
 extern int AggCheckCallContext(FunctionCallInfo fcinfo,
 					MemoryContext *aggcontext);
 
+/*
+ * fmgr_hook
+ * ----------
+ * This hook allows plugin modules to hook events of function calls.
+ * It enables to switch some of its internal state (such as privilege
+ * of the client) during execution of the hooked function.
+ *
+ * The plugin module has to set up two hooks for this feature at least.
+ * The one is 'needs_fmgr_hook' which enables to inform whether the plugin
+ * module wants to hook the supplied function, or not.
+ * It returns 'true', if the supplied function shall be hooked. Elsewhere,
+ * 'false' shall be returned.
+ *
+ * The other hook is 'fmgr_hook' that enables plugin modules to acquire
+ * control when the supplied function is started, ended and aborted.
+ * It takes three arguments: event type (defined as FmgrHookEventType),
+ * FmgrInfo and pointer to the private data field.
+ *
+ * FHET_START event type allows plugins to acquire control just before
+ * invocation of the hooked function for each time. In the first call,
+ * the private data shall be initialized to (Datum)0. If the plugin
+ * module wants palloc(), it should use FmgrInfo->mcxt instead of the
+ * CurrentMemoryContext in the first call, because it is not available
+ * on the later invocations.
+ *
+ * FHET_END and FHET_ABORT allow plugins to acquire control just after
+ * invocation of the hooked function for each time. If necessary, the
+ * plugin module can restore its internal state.
+ */
+typedef enum FmgrHookEventType
+{
+	FHET_START,
+	FHET_END,
+	FHET_ABORT
+} FmgrHookEventType;
+
+typedef bool (*needs_fmgr_hook_type)(Oid fn_oid);
+
+typedef void (*fmgr_hook_type)(FmgrHookEventType event,
+							   FmgrInfo *flinfo, Datum *private);
+
+extern PGDLLIMPORT needs_fmgr_hook_type	needs_fmgr_hook;
+extern PGDLLIMPORT fmgr_hook_type		fmgr_hook;
+
+#define FmgrHookIsNeeded(fn_oid)							\
+	(!needs_fmgr_hook ? false : (*needs_fmgr_hook)(fn_oid))
 
 /*
  * !!! OLD INTERFACE !!!
diff --git a/src/test/regress/input/security_label.source b/src/test/regress/input/security_label.source
index 810a721..9fb2a2d 100644
--- a/src/test/regress/input/security_label.source
+++ b/src/test/regress/input/security_label.source
@@ -37,6 +37,15 @@ SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified';			-- fail
 -- Load dummy external security provider
 LOAD '@libdir@/dummy_seclabel@DLSUFFIX@';
 
+CREATE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c'
+    AS '@libdir@/dummy_seclabel@DLSUFFIX@', 'dummy_client_label';
+
+CREATE FUNCTION seclabel_regular() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+
+CREATE FUNCTION seclabel_trusted() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+
 --
 -- Test of SECURITY LABEL statement with a plugin
 --
@@ -70,7 +79,26 @@ SELECT objtype, objname, provider, label FROM pg_seclabels
 SECURITY LABEL ON LANGUAGE plpgsql IS NULL;						-- OK
 SECURITY LABEL ON SCHEMA public IS NULL;						-- OK
 
+-- test for trusted procedures
+SECURITY LABEL ON FUNCTION seclabel_regular() IS 'unclassified';	-- OK
+SECURITY LABEL ON FUNCTION seclabel_trusted() IS 'trusted';			-- OK
+
+-- should be 'unclassified' and 'top secret'
+SELECT seclabel_regular(), seclabel_trusted();
+
+-- should be 'unclassified' and 'secret'
+SET SESSION AUTHORIZATION seclabel_user1;
+SELECT seclabel_regular(), seclabel_trusted();
+RESET SESSION AUTHORIZATION;
+
+-- hooked functions shall not be inlined
+EXPLAIN SELECT * FROM seclabel_regular();		-- shall be inlined
+EXPLAIN SELECT * FROM seclabel_trusted();		-- shall not be inlined
+
 -- clean up objects
+RESET SESSION AUTHORIZATION;
+DROP FUNCTION seclabel_regular();
+DROP FUNCTION seclabel_trusted();
 DROP FUNCTION seclabel_four();
 DROP DOMAIN seclabel_domain;
 DROP VIEW seclabel_view1;
diff --git a/src/test/regress/output/security_label.source b/src/test/regress/output/security_label.source
index 4bc803d..fb3809e 100644
--- a/src/test/regress/output/security_label.source
+++ b/src/test/regress/output/security_label.source
@@ -30,7 +30,13 @@ ERROR:  no security label providers have been loaded
 SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified';			-- fail
 ERROR:  no security label providers have been loaded
 -- Load dummy external security provider
-LOAD '@abs_builddir@/dummy_seclabel@DLSUFFIX@';
+LOAD '@libdir@/dummy_seclabel@DLSUFFIX@';
+CREATE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c'
+    AS '@libdir@/dummy_seclabel@DLSUFFIX@', 'dummy_client_label';
+CREATE FUNCTION seclabel_regular() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
+CREATE FUNCTION seclabel_trusted() RETURNS text LANGUAGE 'sql'
+    AS 'SELECT dummy_client_label()';
 --
 -- Test of SECURITY LABEL statement with a plugin
 --
@@ -75,7 +81,42 @@ SELECT objtype, objname, provider, label FROM pg_seclabels
 
 SECURITY LABEL ON LANGUAGE plpgsql IS NULL;						-- OK
 SECURITY LABEL ON SCHEMA public IS NULL;						-- OK
+-- test for trusted procedures
+SECURITY LABEL ON FUNCTION seclabel_regular() IS 'unclassified';	-- OK
+SECURITY LABEL ON FUNCTION seclabel_trusted() IS 'trusted';			-- OK
+-- should be 'unclassified' and 'top secret'
+SELECT seclabel_regular(), seclabel_trusted();
+ seclabel_regular | seclabel_trusted 
+------------------+------------------
+ unclassified     | top secret
+(1 row)
+
+-- should be 'unclassified' and 'secret'
+SET SESSION AUTHORIZATION seclabel_user1;
+SELECT seclabel_regular(), seclabel_trusted();
+ seclabel_regular | seclabel_trusted 
+------------------+------------------
+ unclassified     | secret
+(1 row)
+
+RESET SESSION AUTHORIZATION;
+-- hooked functions shall not be inlined
+EXPLAIN SELECT * FROM seclabel_regular();		-- shall be inlined
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Function Scan on dummy_client_label seclabel_regular  (cost=0.00..0.01 rows=1 width=32)
+(1 row)
+
+EXPLAIN SELECT * FROM seclabel_trusted();		-- shall not be inlined
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Function Scan on seclabel_trusted  (cost=0.25..0.26 rows=1 width=32)
+(1 row)
+
 -- clean up objects
+RESET SESSION AUTHORIZATION;
+DROP FUNCTION seclabel_regular();
+DROP FUNCTION seclabel_trusted();
 DROP FUNCTION seclabel_four();
 DROP DOMAIN seclabel_domain;
 DROP VIEW seclabel_view1;
#12Robert Haas
robertmhaas@gmail.com
In reply to: KaiGai Kohei (#11)
Re: Label switcher function

2010/12/7 KaiGai Kohei <kaigai@ak.jp.nec.com>:

Thanks for your reviewing.

The attached patch is a revised version.
I don't have any objections to your comments.

This failed to update the security_label docs, but I don't think it's
a requirement that a hook have regression testing the way we require
for an SQL statement, so I just committed this without that part.

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

#13Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#12)
Re: Label switcher function

On Mon, Dec 13, 2010 at 7:17 PM, Robert Haas <robertmhaas@gmail.com> wrote:

2010/12/7 KaiGai Kohei <kaigai@ak.jp.nec.com>:

Thanks for your reviewing.

The attached patch is a revised version.
I don't have any objections to your comments.

This failed to update the security_label docs, but I don't think it's
a requirement that a hook have regression testing the way we require
for an SQL statement, so I just committed this without that part.

Err, dummy_seclabel, not security_label.

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