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<kai...@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 <kai...@ak.jp.nec.com>
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_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,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_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) + +-- 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;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers