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<kai...@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 <kai...@ak.jp.nec.com>
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>kai...@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_secla...@dlsuffix@'; +CREATE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c' + AS '@libdir@/dummy_secla...@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_secla...@dlsuffix@'; +LOAD '@libdir@/dummy_secla...@dlsuffix@'; +CREATE FUNCTION dummy_client_label() RETURNS text LANGUAGE 'c' + AS '@libdir@/dummy_secla...@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;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers