pgsql-switcher-function.1.patch
application/octect-stream
Filename: pgsql-switcher-function.1.patch
Type: application/octect-stream
Part: 0
Message:
Label switcher function
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| contrib/dummy_seclabel/dummy_seclabel.c | 61 | 1 |
| src/backend/access/transam/xact.c | 13 | 0 |
| src/backend/commands/seclabel.c | 142 | 4 |
| src/backend/optimizer/util/clauses.c | 9 | 0 |
| src/backend/utils/fmgr/fmgr.c | 20 | 1 |
| src/include/commands/seclabel.h | 18 | 2 |
| src/test/regress/input/security_label.source | 20 | 0 |
| src/test/regress/output/security_label.source | 21 | 1 |
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;