diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index 06324513bf7..de49593776b 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -145,8 +145,12 @@ su - postgres libperl library must be a shared library also on most platforms. This appears to be the default in recent Perl versions, but it was not in earlier versions, and in - general it is the choice of whomever installed Perl at your - site. + any case it is the choice of whomever installed Perl at your site. + If you intend to make more than incidental use of + PL/Perl, you should ensure that the + Perl installation was built with the + usemultiplicity option enabled (perl -V + will show whether this is the case). diff --git a/doc/src/sgml/plperl.sgml b/doc/src/sgml/plperl.sgml index aa3838698d8..c6516c2b122 100644 --- a/doc/src/sgml/plperl.sgml +++ b/doc/src/sgml/plperl.sgml @@ -261,21 +261,31 @@ CREATE FUNCTION badfunc() RETURNS integer AS ' - - For security reasons, to stop a leak of privileged operations from - PL/PerlU to PL/Perl, these two languages - have to run in separate instances of the Perl interpreter. If your - Perl installation has been appropriately compiled, this is not a problem. - However, not all installations are compiled with the requisite flags. - If PostgreSQL detects that this is the case then it will - not start a second interpreter, but instead create an error. In - consequence, in such an installation, you cannot use both - PL/PerlU and PL/Perl in the same backend - process. The remedy for this is to obtain a Perl installation created - with the appropriate flags, namely either usemultiplicity or - both usethreads and useithreads. - For more details,see the perlembed manual page. - + + While PL/Perl functions run in a separate Perl + interpreter for each SQL role, all PL/PerlU functions + executed in a given session run in a single Perl interpreter (which is + not any of the ones used for PL/Perl functions). + This allows PL/PerlU functions to share data freely, + but no communication can occur between PL/Perl and + PL/PerlU functions. + + + + + + Perl cannot support multiple interpreters within one process unless + it was built with the appropriate flags, namely either + usemultiplicity or useithreads. + (usemultiplicity is preferred unless you actually need + to use threads. For more details, see the + perlembed man page.) + If PL/Perl is used with a copy of Perl that was not built + this way, then it is only possible to have one Perl interpreter per + session, and so any one session can only execute either + PL/PerlU functions, or PL/Perl functions + that are all called by the same SQL role. + @@ -313,6 +323,23 @@ CREATE FUNCTION badfunc() RETURNS integer AS ' + + + For security reasons, PL/Perl executes functions called by any one SQL role + in a separate Perl interpreter for that role. This prevents accidental or + malicious interference by one user with the behavior of another user's + PL/Perl functions. Each such interpreter has its own value of the + %_SHARED variable and other global state. Thus, two + PL/Perl functions will share the same value of %_SHARED + if and only if they are executed by the same SQL role. In an application + wherein a single session executes code under multiple SQL roles (via + SECURITY DEFINER functions, use of SET ROLE, etc) + you may need to take explicit steps to ensure that PL/Perl functions can + share data via %_SHARED. To do that, make sure that + functions that should communicate are owned by the same user, and mark + them SECURITY DEFINER. You must of course take care that + such functions can't be used to do anything unintended. + diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml index d72e275ae77..f02e203e903 100644 --- a/doc/src/sgml/pltcl.sgml +++ b/doc/src/sgml/pltcl.sgml @@ -199,14 +199,36 @@ CREATE FUNCTION overpaid(employee) RETURNS boolean AS ' Sometimes it is useful to have some global data that is held between two calls to a function or is shared between different functions. - This is easily done since - all PL/Tcl functions executed in one session share the same - safe Tcl interpreter. So, any global Tcl variable is accessible to - all PL/Tcl function calls and will persist for the duration of the - SQL session. (Note that PL/TclU functions likewise share - global data, but they are in a different Tcl interpreter and cannot - communicate with PL/Tcl functions.) + This is easily done in PL/Tcl, but there are some restrictions that + must be understood. + + + For security reasons, PL/Tcl executes functions called by any one SQL + role in a separate Tcl interpreter for that role. This prevents + accidental or malicious interference by one user with the behavior of + another user's PL/Tcl functions. Each such interpreter will have its own + values for any global Tcl variables. Thus, two PL/Tcl + functions will share the same global variables if and only if they are + executed by the same SQL role. In an application wherein a single + session executes code under multiple SQL roles (via SECURITY + DEFINER functions, use of SET ROLE, etc) you may need to + take explicit steps to ensure that PL/Tcl functions can share data. To + do that, make sure that functions that should communicate are owned by + the same user, and mark them SECURITY DEFINER. You must of + course take care that such functions can't be used to do anything + unintended. + + + + All PL/TclU functions used in a session execute in the same Tcl + interpreter, which of course is distinct from the interpreter(s) + used for PL/Tcl functions. So global data is automatically shared + between PL/TclU functions. This is not considered a security risk + because all PL/TclU functions execute at the same trust level, + namely that of a database superuser. + + To help protect PL/Tcl functions from unintentionally interfering with each other, a global @@ -214,9 +236,11 @@ CREATE FUNCTION overpaid(employee) RETURNS boolean AS ' command. The global name of this variable is the function's internal name, and the local name is GD. It is recommended that GD be used - for private data of a function. Use regular Tcl global variables - only for values that you specifically intend to be shared among multiple - functions. + for persistent private data of a function. Use regular Tcl global + variables only for values that you specifically intend to be shared among + multiple functions. (Note that the GD arrays are only + global within a particular interpreter, so they do not bypass the + security restrictions mentioned above.) @@ -648,8 +672,8 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab exists, the module unknown is fetched from the table and loaded into the Tcl interpreter immediately before the first execution of a PL/Tcl function in a database session. (This - happens separately for PL/Tcl and PL/TclU, if both are used, - because separate interpreters are used for the two languages.) + happens separately for each Tcl interpreter, if more than one is + used in a session; see .) While the unknown module could actually contain any diff --git a/doc/src/sgml/release-7.4.sgml b/doc/src/sgml/release-7.4.sgml index 2c52be70064..226275bf320 100644 --- a/doc/src/sgml/release-7.4.sgml +++ b/doc/src/sgml/release-7.4.sgml @@ -37,6 +37,43 @@ + + + Use a separate interpreter for each calling SQL userid in PL/Perl and + PL/Tcl (Tom Lane) + + + + This change prevents security problems that can be caused by subverting + Perl or Tcl code that will be executed later in the same session under + another SQL user identity (for example, within a SECURITY + DEFINER function). Most scripting languages offer numerous ways that + that might be done, such as redefining standard functions or operators + called by the target function. Without this change, any SQL user with + Perl or Tcl language usage rights can do essentially anything with the + SQL privileges of the target function's owner. + + + + The cost of this change is that intentional communication among Perl + and Tcl functions becomes more difficult. To provide an escape hatch, + PL/PerlU and PL/TclU functions continue to use only one interpreter + per session. This is not considered a security issue since all such + functions execute at the trust level of a database superuser already. + + + + It is likely that third-party procedural languages that claim to offer + trusted execution have similar security issues. We advise contacting + the authors of any PL you are depending on for security-critical + purposes. + + + + Our thanks to Tim Bunce for pointing out this issue (CVE-2010-3433). + + + Prevent possible crashes in pg_get_expr() by disallowing diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 5107de82c2e..2c4b581ffc0 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -48,6 +48,7 @@ #include "executor/spi.h" #include "commands/trigger.h" #include "fmgr.h" +#include "miscadmin.h" #include "mb/pg_wchar.h" #include "access/heapam.h" #include "tcop/tcopprot.h" @@ -56,6 +57,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "utils/hsearch.h" +#include "utils/lsyscache.h" /* perl stuff */ #include "EXTERN.h" @@ -72,6 +74,40 @@ /* defines PLPERL_SET_OPMASK */ #include "plperl_opmask.h" +EXTERN_C void boot_DynaLoader(pTHX_ CV *cv); +EXTERN_C void boot_SPI(pTHX_ CV *cv); + + +/********************************************************************** + * Information associated with a Perl interpreter. We have one interpreter + * that is used for all plperlu (untrusted) functions. For plperl (trusted) + * functions, there is a separate interpreter for each effective SQL userid. + * (This is needed to ensure that an unprivileged user can't inject Perl code + * that'll be executed with the privileges of some other SQL user.) + * + * The plperl_interp_desc structs are kept in a Postgres hash table indexed + * by userid OID, with OID 0 used for the single untrusted interpreter. + * + * We start out by creating a "held" interpreter, which we initialize + * only as far as we can do without deciding if it will be trusted or + * untrusted. Later, when we first need to run a plperl or plperlu + * function, we complete the initialization appropriately and move the + * PerlInterpreter pointer into the plperl_interp_hash hashtable. If after + * that we need more interpreters, we create them as needed if we can, or + * fail if the Perl build doesn't support multiple interpreters. + * + * The reason for all the dancing about with a held interpreter is to make + * it possible for people to preload a lot of Perl code at postmaster startup + * (using plperl.on_init) and then use that code in backends. Of course this + * will only work for the first interpreter created in any backend, but it's + * still useful with that restriction. + **********************************************************************/ +typedef struct plperl_interp_desc +{ + Oid user_id; /* Hash key (must be first!) */ + PerlInterpreter *interp; /* The interpreter */ +} plperl_interp_desc; + /********************************************************************** * The information we cache about loaded procedures @@ -81,6 +117,7 @@ typedef struct plperl_proc_desc char *proname; TransactionId fn_xmin; CommandId fn_cmin; + plperl_interp_desc *interp; /* interpreter it's created in */ bool lanpltrusted; FmgrInfo result_in_func; Oid result_in_elem; @@ -95,56 +132,68 @@ typedef struct plperl_proc_desc * Global data **********************************************************************/ -typedef enum -{ - INTERP_NONE, - INTERP_HELD, - INTERP_TRUSTED, - INTERP_UNTRUSTED, - INTERP_BOTH -} InterpState; - -static InterpState interp_state = INTERP_NONE; -static bool can_run_two = false; - -static int plperl_firstcall = 1; -static bool plperl_safe_init_done = false; -static PerlInterpreter *plperl_trusted_interp = NULL; -static PerlInterpreter *plperl_untrusted_interp = NULL; -static PerlInterpreter *plperl_held_interp = NULL; -static OP *(*pp_require_orig) (pTHX) = NULL; -static OP *pp_require_safe(pTHX); -static bool trusted_context; +static bool plperl_firstcall = true; +static HTAB *plperl_interp_hash = NULL; static HTAB *plperl_proc_hash = NULL; +static plperl_interp_desc *plperl_active_interp = NULL; +/* If we have an unassigned "held" interpreter, it's stored here */ +static PerlInterpreter *plperl_held_interp = NULL; + +static OP *(*pp_require_orig) (pTHX) = NULL; static char plperl_opmask[MAXO]; -static void set_interp_require(void); /********************************************************************** * Forward declarations **********************************************************************/ -static void plperl_init_all(void); -static void plperl_init_interp(void); - Datum plperl_call_handler(PG_FUNCTION_ARGS); void plperl_init(void); +static PerlInterpreter *plperl_init_interp(void); +static void set_interp_require(bool trusted); + static Datum plperl_func_handler(PG_FUNCTION_ARGS); static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger); static SV *plperl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc); static void plperl_init_shared_libs(pTHX); -static void plperl_safe_init(void); +static void plperl_trusted_init(void); +static void plperl_untrusted_init(void); +static void plperl_create_sub(plperl_proc_desc *desc, char *s, Oid fn_oid); static char *strip_trailing_ws(const char *msg); +static OP *pp_require_safe(pTHX); +static void activate_interpreter(plperl_interp_desc *interp_desc); -/* hash table entry for proc desc */ -typedef struct plperl_proc_entry +/********************************************************************** + * For speedy lookup, we maintain a hash table mapping from + * function OID + trigger flag + user OID to plperl_proc_desc pointers. + * The reason the plperl_proc_desc struct isn't directly part of the hash + * entry is to simplify recovery from errors during compile_plperl_function. + * + * Note: if the same function is called by multiple userIDs within a session, + * there will be a separate plperl_proc_desc entry for each userID in the case + * of plperl functions, but only one entry for plperlu functions, because we + * set user_id = 0 for that case. If the user redeclares the same function + * from plperl to plperlu or vice versa, there might be multiple + * plperl_proc_ptr entries in the hashtable, but only one is valid. + **********************************************************************/ +typedef struct plperl_proc_key { - char proc_name[NAMEDATALEN]; - plperl_proc_desc *proc_data; -} plperl_proc_entry; + Oid proc_id; /* Function OID */ + /* + * is_trigger is really a bool, but declare as Oid to ensure this struct + * contains no padding + */ + Oid is_trigger; /* is it a trigger function? */ + Oid user_id; /* User calling the function, or 0 */ +} plperl_proc_key; +typedef struct plperl_proc_ptr +{ + plperl_proc_key proc_key; /* Hash key (must be first!) */ + plperl_proc_desc *proc_ptr; +} plperl_proc_ptr; /* * This routine is a crock, and so is everyplace that calls it. The problem @@ -181,24 +230,32 @@ plperl_init(void) if (!plperl_firstcall) return; - MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(plperl_interp_desc); + hash_ctl.hash = tag_hash; + plperl_interp_hash = hash_create("PL/Perl interpreters", + 8, + &hash_ctl, + HASH_ELEM | HASH_FUNCTION); - hash_ctl.keysize = NAMEDATALEN; - hash_ctl.entrysize = sizeof(plperl_proc_entry); - - plperl_proc_hash = hash_create("PLPerl Procedures", + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(plperl_proc_key); + hash_ctl.entrysize = sizeof(plperl_proc_ptr); + hash_ctl.hash = tag_hash; + plperl_proc_hash = hash_create("PL/Perl procedures", 32, &hash_ctl, - HASH_ELEM); + HASH_ELEM | HASH_FUNCTION); /************************************************************ - * Now recreate a new Perl interpreter + * Create the Perl interpreter ************************************************************/ PLPERL_SET_OPMASK(plperl_opmask); - plperl_init_interp(); + plperl_held_interp = plperl_init_interp(); - plperl_firstcall = 0; + plperl_firstcall = false; } /********************************************************************** @@ -224,17 +281,10 @@ plperl_init_all(void) #define PLC_TRUSTED \ "require strict; " -#define TEST_FOR_MULTI \ - "use Config; " \ - "$Config{usemultiplicity} eq 'define' or " \ - "($Config{usethreads} eq 'define' " \ - " and $Config{useithreads} eq 'define')" - - static void -set_interp_require(void) +set_interp_require(bool trusted) { - if (trusted_context) + if (trusted) { PL_ppaddr[OP_REQUIRE] = pp_require_safe; PL_ppaddr[OP_DOFILE] = pp_require_safe; @@ -246,97 +296,128 @@ set_interp_require(void) } } -/******************************************************************** - * - * We start out by creating a "held" interpreter that we can use in - * trusted or untrusted mode (but not both) as the need arises. Later, we - * assign that interpreter if it is available to either the trusted or - * untrusted interpreter. If it has already been assigned, and we need to - * create the other interpreter, we do that if we can, or error out. - * We detect if it is safe to run two interpreters during the setup of the - * dummy interpreter. +/* + * Select and activate an appropriate Perl interpreter. */ - - static void -check_interp(bool trusted) +select_perl_context(bool trusted) { - if (interp_state == INTERP_HELD) + Oid user_id; + plperl_interp_desc *interp_desc; + bool found; + PerlInterpreter *interp = NULL; + + /* Find or create the interpreter hashtable entry for this userid */ + if (trusted) + user_id = GetUserId(); + else + user_id = InvalidOid; + + interp_desc = hash_search(plperl_interp_hash, &user_id, + HASH_ENTER, + &found); + if (!found) { - if (trusted) - { - plperl_trusted_interp = plperl_held_interp; - interp_state = INTERP_TRUSTED; - } - else - { - plperl_untrusted_interp = plperl_held_interp; - interp_state = INTERP_UNTRUSTED; - } - plperl_held_interp = NULL; - trusted_context = trusted; - set_interp_require(); + /* Initialize newly-created hashtable entry */ + interp_desc->interp = NULL; } - else if (interp_state == INTERP_BOTH || - (trusted && interp_state == INTERP_TRUSTED) || - (!trusted && interp_state == INTERP_UNTRUSTED)) + + /* + * Quick exit if already have an interpreter + */ + if (interp_desc->interp) { - if (trusted_context != trusted) - { - if (trusted) - PERL_SET_CONTEXT(plperl_trusted_interp); - else - PERL_SET_CONTEXT(plperl_untrusted_interp); - trusted_context = trusted; - set_interp_require(); - } + activate_interpreter(interp_desc); + return; } - else if (can_run_two) + + /* + * adopt held interp if free, else create new one if possible + */ + if (plperl_held_interp != NULL) { - PERL_SET_CONTEXT(plperl_held_interp); - plperl_init_interp(); - if (trusted) - plperl_trusted_interp = plperl_held_interp; - else - plperl_untrusted_interp = plperl_held_interp; - interp_state = INTERP_BOTH; + /* first actual use of a perl interpreter */ + interp = plperl_held_interp; + + /* + * Reset the plperl_held_interp pointer first; if we fail during init + * we don't want to try again with the partially-initialized interp. + */ plperl_held_interp = NULL; - trusted_context = trusted; - set_interp_require(); + + if (trusted) + plperl_trusted_init(); + else + plperl_untrusted_init(); } else { - elog(ERROR, - "can not allocate second Perl interpreter on this platform"); +#ifdef MULTIPLICITY + /* + * plperl_init_interp will change Perl's idea of the active + * interpreter. Reset plperl_active_interp temporarily, so that if we + * hit an error partway through here, we'll make sure to switch back + * to a non-broken interpreter before running any other Perl + * functions. + */ + plperl_active_interp = NULL; - } + /* Now build the new interpreter */ + interp = plperl_init_interp(); -} - - -static void -restore_context(bool old_context) -{ - if (trusted_context != old_context) - { - if (old_context) - PERL_SET_CONTEXT(plperl_trusted_interp); + if (trusted) + plperl_trusted_init(); else - PERL_SET_CONTEXT(plperl_untrusted_interp); + plperl_untrusted_init(); +#else + elog(ERROR, + "cannot allocate multiple Perl interpreters on this platform"); +#endif + } - trusted_context = old_context; - set_interp_require(); + set_interp_require(trusted); + + /* Fully initialized, so mark the hashtable entry valid */ + interp_desc->interp = interp; + + /* And mark this as the active interpreter */ + plperl_active_interp = interp_desc; +} + +/* + * Make the specified interpreter the active one + * + * A call with NULL does nothing. This is so that "restoring" to a previously + * null state of plperl_active_interp doesn't result in useless thrashing. + */ +static void +activate_interpreter(plperl_interp_desc *interp_desc) +{ + if (interp_desc && plperl_active_interp != interp_desc) + { + Assert(interp_desc->interp); + PERL_SET_CONTEXT(interp_desc->interp); + /* trusted iff user_id isn't InvalidOid */ + set_interp_require(OidIsValid(interp_desc->user_id)); + plperl_active_interp = interp_desc; } } -/********************************************************************** - * plperl_init_interp() - Create the Perl interpreter - **********************************************************************/ -static void +/* + * Create a new Perl interpreter. + * + * We initialize the interpreter as far as we can without knowing whether + * it will become a trusted or untrusted interpreter; in particular, the + * plperl.on_init code will get executed. Later, either plperl_trusted_init + * or plperl_untrusted_init must be called to complete the initialization. + */ +static PerlInterpreter * plperl_init_interp(void) { + PerlInterpreter *plperl; + static int perl_sys_init_done; - char *embedding[3] = { + static char *embedding[3] = { "", "-e", /* @@ -357,7 +438,7 @@ plperl_init_interp(void) * true when MYMALLOC is set. */ #if defined(PERL_SYS_INIT3) && !defined(MYMALLOC) - if (interp_state == INTERP_NONE) + if (!perl_sys_init_done) { int nargs; char *dummy_perl_env[1]; @@ -366,14 +447,16 @@ plperl_init_interp(void) nargs = 3; dummy_perl_env[0] = NULL; PERL_SYS_INIT3(&nargs, (char ***) &embedding, (char ***) &dummy_perl_env); + perl_sys_init_done = 1; } #endif - plperl_held_interp = perl_alloc(); - if (!plperl_held_interp) + plperl = perl_alloc(); + if (!plperl) elog(ERROR, "could not allocate Perl interpreter"); - perl_construct(plperl_held_interp); + PERL_SET_CONTEXT(plperl); + perl_construct(plperl); /* * Record the original function for the 'require' and 'dofile' opcodes. @@ -390,18 +473,18 @@ plperl_init_interp(void) PL_ppaddr[OP_DOFILE] = pp_require_orig; } - perl_parse(plperl_held_interp, plperl_init_shared_libs, - 3, embedding, NULL); - perl_run(plperl_held_interp); + if (perl_parse(plperl, plperl_init_shared_libs, + 3, embedding, NULL) != 0) + ereport(ERROR, + (errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))), + errcontext("while parsing Perl initialization"))); - if (interp_state == INTERP_NONE) - { - SV *res; + if (perl_run(plperl) != 0) + ereport(ERROR, + (errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))), + errcontext("while running Perl initialization"))); - res = eval_pv(TEST_FOR_MULTI, TRUE); - can_run_two = SvIV(res); - interp_state = INTERP_HELD; - } + return plperl; } @@ -419,7 +502,7 @@ Datum plperl_call_handler(PG_FUNCTION_ARGS) { Datum retval; - bool oldcontext = trusted_context; + plperl_interp_desc *oldinterp; sigjmp_buf save_restart; /************************************************************ @@ -437,16 +520,16 @@ plperl_call_handler(PG_FUNCTION_ARGS) * Determine if called as function or trigger and * call appropriate subhandler ************************************************************/ + oldinterp = plperl_active_interp; memcpy(&save_restart, &Warn_restart, sizeof(save_restart)); if (sigsetjmp(Warn_restart, 1) != 0) { memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); - restore_context(oldcontext); + activate_interpreter(oldinterp); siglongjmp(Warn_restart, 1); } - if (CALLED_AS_TRIGGER(fcinfo)) { ereport(ERROR, @@ -466,7 +549,7 @@ plperl_call_handler(PG_FUNCTION_ARGS) } memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); - restore_context(oldcontext); + activate_interpreter(oldinterp); return retval; } @@ -476,19 +559,13 @@ plperl_call_handler(PG_FUNCTION_ARGS) * create the anonymous subroutine whose text is in the SV. * Returns the SV containing the RV to the closure. **********************************************************************/ -static SV * -plperl_create_sub(char *s, bool trusted) +static void +plperl_create_sub(plperl_proc_desc *prodesc, char *s, Oid fn_oid) { dSP; SV *subref; int count; - if (trusted && !plperl_safe_init_done) - { - plperl_safe_init(); - SPAGAIN; - } - ENTER; SAVETMPS; PUSHMARK(SP); @@ -544,7 +621,7 @@ plperl_create_sub(char *s, bool trusted) FREETMPS; LEAVE; - return subref; + prodesc->reference = subref; } /* @@ -576,8 +653,11 @@ pp_require_safe(pTHX) DIE(aTHX_ "Unable to load %s into plperl", name); } +/* + * Initialize the current Perl interpreter as a trusted interp + */ static void -plperl_safe_init(void) +plperl_trusted_init(void) { HV *stash; SV *sv; @@ -617,9 +697,9 @@ plperl_safe_init(void) PL_ppaddr[OP_REQUIRE] = pp_require_safe; PL_ppaddr[OP_DOFILE] = pp_require_safe; - /* - * prevent (any more) unsafe opcodes being compiled - * PL_op_mask is per interpreter, so this only needs to be set once + /* + * prevent (any more) unsafe opcodes being compiled + * PL_op_mask is per interpreter, so this only needs to be set once */ PL_op_mask = plperl_opmask; /* delete the DynaLoader:: namespace so extensions can't be loaded */ @@ -639,8 +719,17 @@ plperl_safe_init(void) #ifdef PL_stashcache hv_clear(PL_stashcache); #endif +} - plperl_safe_init_done = true; +/* + * Initialize the current Perl interpreter as an untrusted interp + */ +static void +plperl_untrusted_init(void) +{ + /* + * Nothing to do here + */ } @@ -652,10 +741,6 @@ plperl_safe_init(void) * and do the initialization behind perl's back. * **********************************************************************/ - -EXTERN_C void boot_DynaLoader(pTHX_ CV *cv); -EXTERN_C void boot_SPI(pTHX_ CV *cv); - static void plperl_init_shared_libs(pTHX) { @@ -761,7 +846,7 @@ plperl_func_handler(PG_FUNCTION_ARGS) /* Find or compile the function */ prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false); - check_interp(prodesc->lanpltrusted); + activate_interpreter(prodesc->interp); /************************************************************ * Call the Perl function @@ -797,51 +882,14 @@ plperl_func_handler(PG_FUNCTION_ARGS) } -/********************************************************************** - * compile_plperl_function - compile (or hopefully just look up) function - **********************************************************************/ -static plperl_proc_desc * -compile_plperl_function(Oid fn_oid, bool is_trigger) +static bool +validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup) { - HeapTuple procTup; - Form_pg_proc procStruct; - char internal_proname[64]; - int proname_len; - plperl_proc_desc *prodesc = NULL; - int i; - plperl_proc_entry *hash_entry; - bool found; - bool oldcontext = trusted_context; - - /* We'll need the pg_proc tuple in any case... */ - procTup = SearchSysCache(PROCOID, - ObjectIdGetDatum(fn_oid), - 0, 0, 0); - if (!HeapTupleIsValid(procTup)) - elog(ERROR, "cache lookup failed for function %u", fn_oid); - procStruct = (Form_pg_proc) GETSTRUCT(procTup); - - /************************************************************ - * Build our internal proc name from the functions Oid - ************************************************************/ - if (!is_trigger) - sprintf(internal_proname, "__PLPerl_proc_%u", fn_oid); - else - sprintf(internal_proname, "__PLPerl_proc_%u_trigger", fn_oid); - proname_len = strlen(internal_proname); - - /************************************************************ - * Lookup the internal proc name in the hashtable - ************************************************************/ - hash_entry = hash_search(plperl_proc_hash, internal_proname, - HASH_FIND, NULL); - - if (hash_entry) + if (proc_ptr && proc_ptr->proc_ptr) { + plperl_proc_desc *prodesc = proc_ptr->proc_ptr; bool uptodate; - prodesc = hash_entry->proc_data; - /************************************************************ * If it's present, must check whether it's still up to date. * This is needed because CREATE OR REPLACE FUNCTION can modify the @@ -850,20 +898,68 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data)); - if (!uptodate) + if (uptodate) + return true; + + /* Otherwise, unlink the obsoleted entry from the hashtable ... */ + proc_ptr->proc_ptr = NULL; + /* ... and throw it away */ + if (prodesc->reference) { - hash_search(plperl_proc_hash, internal_proname, - HASH_REMOVE, NULL); - if (prodesc->reference) - { - check_interp(prodesc->lanpltrusted); - SvREFCNT_dec(prodesc->reference); - restore_context(oldcontext); - } - free(prodesc->proname); - free(prodesc); - prodesc = NULL; + plperl_interp_desc *oldinterp = plperl_active_interp; + + activate_interpreter(prodesc->interp); + SvREFCNT_dec(prodesc->reference); + activate_interpreter(oldinterp); } + free(prodesc->proname); + free(prodesc); + } + + return false; +} + + +/********************************************************************** + * compile_plperl_function - compile (or hopefully just look up) function + **********************************************************************/ +static plperl_proc_desc * +compile_plperl_function(Oid fn_oid, bool is_trigger) +{ + HeapTuple procTup; + Form_pg_proc procStruct; + plperl_proc_key proc_key; + plperl_proc_ptr *proc_ptr; + plperl_proc_desc *prodesc = NULL; + int i; + plperl_interp_desc *oldinterp = plperl_active_interp; + + /* We'll need the pg_proc tuple in any case... */ + procTup = SearchSysCache(PROCOID, + ObjectIdGetDatum(fn_oid), + 0, 0, 0); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for function %u", fn_oid); + procStruct = (Form_pg_proc) GETSTRUCT(procTup); + + /* Try to find function in plperl_proc_hash */ + proc_key.proc_id = fn_oid; + proc_key.is_trigger = is_trigger; + proc_key.user_id = GetUserId(); + + proc_ptr = hash_search(plperl_proc_hash, &proc_key, + HASH_FIND, NULL); + + if (validate_plperl_function(proc_ptr, procTup)) + prodesc = proc_ptr->proc_ptr; + else + { + /* If not found or obsolete, maybe it's plperlu */ + proc_key.user_id = InvalidOid; + proc_ptr = hash_search(plperl_proc_hash, &proc_key, + HASH_FIND, NULL); + if (validate_plperl_function(proc_ptr, procTup)) + prodesc = proc_ptr->proc_ptr; } /************************************************************ @@ -891,7 +987,11 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); MemSet(prodesc, 0, sizeof(plperl_proc_desc)); - prodesc->proname = strdup(internal_proname); + prodesc->proname = strdup(NameStr(procStruct->proname)); + if (prodesc->proname == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data); @@ -1032,31 +1132,33 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) PointerGetDatum(&procStruct->prosrc))); /************************************************************ - * Create the procedure in the interpreter + * Create the procedure in the appropriate interpreter ************************************************************/ - check_interp(prodesc->lanpltrusted); + select_perl_context(prodesc->lanpltrusted); - prodesc->reference = - plperl_create_sub(proc_source, prodesc->lanpltrusted); + prodesc->interp = plperl_active_interp; - restore_context(oldcontext); + plperl_create_sub(prodesc, proc_source, fn_oid); + + activate_interpreter(oldinterp); pfree(proc_source); if (!prodesc->reference) { free(prodesc->proname); free(prodesc); - elog(ERROR, "could not create internal procedure \"%s\"", - internal_proname); + elog(ERROR, "could not create PL/Perl internal procedure"); } /************************************************************ - * Add the proc description block to the hashtable + * OK, link the procedure into the correct hashtable entry ************************************************************/ - hash_entry = hash_search(plperl_proc_hash, internal_proname, - HASH_ENTER, &found); - hash_entry->proc_data = prodesc; + proc_key.user_id = prodesc->lanpltrusted ? GetUserId() : InvalidOid; + + proc_ptr = hash_search(plperl_proc_hash, &proc_key, + HASH_ENTER, NULL); + proc_ptr->proc_ptr = prodesc; } ReleaseSysCache(procTup); diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index b6e54777bab..bec4c24b7b4 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -50,7 +50,6 @@ #include "access/heapam.h" #include "catalog/namespace.h" -#include "catalog/pg_language.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/trigger.h" @@ -97,6 +96,24 @@ utf_e2u(unsigned char *src) #define UTF_E2U(x) (x) #endif /* PLTCL_UTF */ + +/********************************************************************** + * Information associated with a Tcl interpreter. We have one interpreter + * that is used for all pltclu (untrusted) functions. For pltcl (trusted) + * functions, there is a separate interpreter for each effective SQL userid. + * (This is needed to ensure that an unprivileged user can't inject Tcl code + * that'll be executed with the privileges of some other SQL user.) + * + * The pltcl_interp_desc structs are kept in a Postgres hash table indexed + * by userid OID, with OID 0 used for the single untrusted interpreter. + **********************************************************************/ +typedef struct pltcl_interp_desc +{ + Oid user_id; /* Hash key (must be first!) */ + Tcl_Interp *interp; /* The interpreter */ + Tcl_HashTable query_hash; /* pltcl_query_desc structs */ +} pltcl_interp_desc; + /********************************************************************** * The information we cache about loaded procedures **********************************************************************/ @@ -106,6 +123,7 @@ typedef struct pltcl_proc_desc TransactionId fn_xmin; CommandId fn_cmin; bool lanpltrusted; + pltcl_interp_desc *interp_desc; FmgrInfo result_in_func; Oid result_in_elem; int nargs; @@ -114,7 +132,6 @@ typedef struct pltcl_proc_desc int arg_is_rel[FUNC_MAX_ARGS]; } pltcl_proc_desc; - /********************************************************************** * The information we cache about prepared and saved plans **********************************************************************/ @@ -128,40 +145,65 @@ typedef struct pltcl_query_desc Oid *argtypelems; } pltcl_query_desc; +/********************************************************************** + * For speedy lookup, we maintain a hash table mapping from + * function OID + trigger OID + user OID to pltcl_proc_desc pointers. + * The reason the pltcl_proc_desc struct isn't directly part of the hash + * entry is to simplify recovery from errors during compile_pltcl_function. + * + * Note: if the same function is called by multiple userIDs within a session, + * there will be a separate pltcl_proc_desc entry for each userID in the case + * of pltcl functions, but only one entry for pltclu functions, because we + * set user_id = 0 for that case. + **********************************************************************/ +typedef struct pltcl_proc_key +{ + Oid proc_id; /* Function OID */ + Oid trig_id; /* Trigger OID, or 0 if not trigger */ + Oid user_id; /* User calling the function, or 0 */ +} pltcl_proc_key; + +typedef struct pltcl_proc_ptr +{ + pltcl_proc_key proc_key; /* Hash key (must be first!) */ + pltcl_proc_desc *proc_ptr; +} pltcl_proc_ptr; + /********************************************************************** * Global data **********************************************************************/ static bool pltcl_pm_init_done = false; -static bool pltcl_be_norm_init_done = false; -static bool pltcl_be_safe_init_done = false; static int pltcl_call_level = 0; static int pltcl_restart_in_progress = 0; static Tcl_Interp *pltcl_hold_interp = NULL; -static Tcl_Interp *pltcl_norm_interp = NULL; -static Tcl_Interp *pltcl_safe_interp = NULL; -static Tcl_HashTable *pltcl_proc_hash = NULL; -static Tcl_HashTable *pltcl_norm_query_hash = NULL; -static Tcl_HashTable *pltcl_safe_query_hash = NULL; +static HTAB *pltcl_interp_htab = NULL; +static HTAB *pltcl_proc_htab = NULL; + +/* these are saved and restored by pltcl_handler */ static FunctionCallInfo pltcl_current_fcinfo = NULL; +static pltcl_proc_desc *pltcl_current_prodesc = NULL; /********************************************************************** * Forward declarations **********************************************************************/ -static void pltcl_init_interp(Tcl_Interp *interp); -static Tcl_Interp *pltcl_fetch_interp(bool pltrusted); -static void pltcl_init_load_unknown(Tcl_Interp *interp); - Datum pltcl_call_handler(PG_FUNCTION_ARGS); Datum pltclu_call_handler(PG_FUNCTION_ARGS); void pltcl_init(void); -static Datum pltcl_func_handler(PG_FUNCTION_ARGS); +static void pltcl_init_interp(pltcl_interp_desc *interp_desc, bool pltrusted); +static pltcl_interp_desc *pltcl_fetch_interp(bool pltrusted); +static void pltcl_init_load_unknown(Tcl_Interp *interp); -static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS); +static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted); -static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid); +static Datum pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted); + +static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted); + +static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid, + bool pltrusted); static int pltcl_elog(ClientData cdata, Tcl_Interp *interp, int argc, CONST84 char *argv[]); @@ -212,6 +254,8 @@ perm_fmgr_info(Oid functionId, FmgrInfo *finfo) void pltcl_init(void) { + HASHCTL hash_ctl; + /************************************************************ * Do initialization only once ************************************************************/ @@ -223,47 +267,62 @@ pltcl_init(void) * stdout and stderr on DeleteInterp ************************************************************/ if ((pltcl_hold_interp = Tcl_CreateInterp()) == NULL) - elog(ERROR, "could not create \"hold\" interpreter"); + elog(ERROR, "could not create master Tcl interpreter"); if (Tcl_Init(pltcl_hold_interp) == TCL_ERROR) - elog(ERROR, "could not initialize \"hold\" interpreter"); + elog(ERROR, "could not initialize master Tcl interpreter"); /************************************************************ - * Create the two slave interpreters. Note: Tcl automatically does - * Tcl_Init on the normal slave, and it's not wanted for the safe slave. + * Create the hash table for working interpreters ************************************************************/ - if ((pltcl_norm_interp = - Tcl_CreateSlave(pltcl_hold_interp, "norm", 0)) == NULL) - elog(ERROR, "could not create \"normal\" interpreter"); - pltcl_init_interp(pltcl_norm_interp); - - if ((pltcl_safe_interp = - Tcl_CreateSlave(pltcl_hold_interp, "safe", 1)) == NULL) - elog(ERROR, "could not create \"safe\" interpreter"); - pltcl_init_interp(pltcl_safe_interp); + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(pltcl_interp_desc); + hash_ctl.hash = tag_hash; + pltcl_interp_htab = hash_create("PL/Tcl interpreters", + 8, + &hash_ctl, + HASH_ELEM | HASH_FUNCTION); /************************************************************ - * Initialize the proc and query hash tables + * Create the hash table for function lookup ************************************************************/ - pltcl_proc_hash = (Tcl_HashTable *) malloc(sizeof(Tcl_HashTable)); - pltcl_norm_query_hash = (Tcl_HashTable *) malloc(sizeof(Tcl_HashTable)); - pltcl_safe_query_hash = (Tcl_HashTable *) malloc(sizeof(Tcl_HashTable)); - Tcl_InitHashTable(pltcl_proc_hash, TCL_STRING_KEYS); - Tcl_InitHashTable(pltcl_norm_query_hash, TCL_STRING_KEYS); - Tcl_InitHashTable(pltcl_safe_query_hash, TCL_STRING_KEYS); + memset(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(pltcl_proc_key); + hash_ctl.entrysize = sizeof(pltcl_proc_ptr); + hash_ctl.hash = tag_hash; + pltcl_proc_htab = hash_create("PL/Tcl functions", + 100, + &hash_ctl, + HASH_ELEM | HASH_FUNCTION); pltcl_pm_init_done = true; } /********************************************************************** - * pltcl_init_interp() - initialize a Tcl interpreter - * - * The work done here must be safe to do in the postmaster process, - * in case the pltcl library is preloaded in the postmaster. Note - * that this is applied separately to the "normal" and "safe" interpreters. + * pltcl_init_interp() - initialize a new Tcl interpreter **********************************************************************/ static void -pltcl_init_interp(Tcl_Interp *interp) +pltcl_init_interp(pltcl_interp_desc *interp_desc, bool pltrusted) { + Tcl_Interp *interp; + char interpname[32]; + + /************************************************************ + * Create the Tcl interpreter as a slave of pltcl_hold_interp. + * Note: Tcl automatically does Tcl_Init in the untrusted case, + * and it's not wanted in the trusted case. + ************************************************************/ + snprintf(interpname, sizeof(interpname), "slave_%u", interp_desc->user_id); + if ((interp = Tcl_CreateSlave(pltcl_hold_interp, interpname, + pltrusted ? 1 : 0)) == NULL) + elog(ERROR, "could not create slave Tcl interpreter"); + interp_desc->interp = interp; + + /************************************************************ + * Initialize the query hash table associated with interpreter + ************************************************************/ + Tcl_InitHashTable(&interp_desc->query_hash, TCL_STRING_KEYS); + /************************************************************ * Install the commands for SPI support in the interpreter ************************************************************/ @@ -284,43 +343,39 @@ pltcl_init_interp(Tcl_Interp *interp) pltcl_SPI_execp, NULL, NULL); Tcl_CreateCommand(interp, "spi_lastoid", pltcl_SPI_lastoid, NULL, NULL); + + /************************************************************ + * Try to load the unknown procedure from pltcl_modules + ************************************************************/ + pltcl_init_load_unknown(interp); } /********************************************************************** * pltcl_fetch_interp() - fetch the Tcl interpreter to use for a function * * This also takes care of any on-first-use initialization required. - * The initialization work done here can't be done in the postmaster, and - * hence is not safe to do at library load time, because it may invoke - * arbitrary user-defined code. * Note: we assume caller has already connected to SPI. **********************************************************************/ -static Tcl_Interp * +static pltcl_interp_desc * pltcl_fetch_interp(bool pltrusted) { - Tcl_Interp *interp; + Oid user_id; + pltcl_interp_desc *interp_desc; + bool found; - /* On first use, we try to load the unknown procedure from pltcl_modules */ + /* Find or create the interpreter hashtable entry for this userid */ if (pltrusted) - { - interp = pltcl_safe_interp; - if (!pltcl_be_safe_init_done) - { - pltcl_init_load_unknown(interp); - pltcl_be_safe_init_done = true; - } - } + user_id = GetUserId(); else - { - interp = pltcl_norm_interp; - if (!pltcl_be_norm_init_done) - { - pltcl_init_load_unknown(interp); - pltcl_be_norm_init_done = true; - } - } + user_id = InvalidOid; - return interp; + interp_desc = hash_search(pltcl_interp_htab, &user_id, + HASH_ENTER, + &found); + if (!found) + pltcl_init_interp(interp_desc, pltrusted); + + return interp_desc; } /********************************************************************** @@ -467,9 +522,29 @@ PG_FUNCTION_INFO_V1(pltcl_call_handler); /* keep non-static */ Datum pltcl_call_handler(PG_FUNCTION_ARGS) +{ + return pltcl_handler(fcinfo, true); +} + +/* + * Alternative handler for unsafe functions + */ +PG_FUNCTION_INFO_V1(pltclu_call_handler); + +/* keep non-static */ +Datum +pltclu_call_handler(PG_FUNCTION_ARGS) +{ + return pltcl_handler(fcinfo, false); +} + + +static Datum +pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted) { Datum retval; FunctionCallInfo save_fcinfo; + pltcl_proc_desc *save_prodesc; /************************************************************ * Initialize interpreters if not done previously @@ -492,19 +567,21 @@ pltcl_call_handler(PG_FUNCTION_ARGS) * call appropriate subhandler ************************************************************/ save_fcinfo = pltcl_current_fcinfo; + save_prodesc = pltcl_current_prodesc; if (CALLED_AS_TRIGGER(fcinfo)) { pltcl_current_fcinfo = NULL; - retval = PointerGetDatum(pltcl_trigger_handler(fcinfo)); + retval = PointerGetDatum(pltcl_trigger_handler(fcinfo, pltrusted)); } else { pltcl_current_fcinfo = fcinfo; - retval = pltcl_func_handler(fcinfo); + retval = pltcl_func_handler(fcinfo, pltrusted); } pltcl_current_fcinfo = save_fcinfo; + pltcl_current_prodesc = save_prodesc; pltcl_call_level--; @@ -512,23 +589,11 @@ pltcl_call_handler(PG_FUNCTION_ARGS) } -/* - * Alternate handler for unsafe functions - */ -PG_FUNCTION_INFO_V1(pltclu_call_handler); - -/* keep non-static */ -Datum -pltclu_call_handler(PG_FUNCTION_ARGS) -{ - return pltcl_call_handler(fcinfo); -} - /********************************************************************** * pltcl_func_handler() - Handler for regular function calls **********************************************************************/ static Datum -pltcl_func_handler(PG_FUNCTION_ARGS) +pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted) { pltcl_proc_desc *prodesc; Tcl_Interp *volatile interp; @@ -540,9 +605,12 @@ pltcl_func_handler(PG_FUNCTION_ARGS) sigjmp_buf save_restart; /* Find or compile the function */ - prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid); + prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid, + pltrusted); - interp = pltcl_fetch_interp(prodesc->lanpltrusted); + pltcl_current_prodesc = prodesc; + + interp = prodesc->interp_desc->interp; /************************************************************ * Create the tcl command to call the internal @@ -699,7 +767,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS) * pltcl_trigger_handler() - Handler for trigger calls **********************************************************************/ static HeapTuple -pltcl_trigger_handler(PG_FUNCTION_ARGS) +pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted) { pltcl_proc_desc *prodesc; Tcl_Interp *volatile interp; @@ -724,9 +792,12 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS) /* Find or compile the function */ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, - RelationGetRelid(trigdata->tg_relation)); + RelationGetRelid(trigdata->tg_relation), + pltrusted); - interp = pltcl_fetch_interp(prodesc->lanpltrusted); + pltcl_current_prodesc = prodesc; + + interp = prodesc->interp_desc->interp; tupdesc = trigdata->tg_relation->rd_att; @@ -1040,18 +1111,14 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS) * (InvalidOid) when compiling a plain function. **********************************************************************/ static pltcl_proc_desc * -compile_pltcl_function(Oid fn_oid, Oid tgreloid) +compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted) { - bool is_trigger = OidIsValid(tgreloid); HeapTuple procTup; Form_pg_proc procStruct; - char internal_proname[128]; - Tcl_HashEntry *hashent; - pltcl_proc_desc *prodesc = NULL; - Tcl_Interp *interp; - int i; - int hashnew; - int tcl_rc; + pltcl_proc_key proc_key; + pltcl_proc_ptr *proc_ptr; + bool found; + pltcl_proc_desc *prodesc; /* We'll need the pg_proc tuple in any case... */ procTup = SearchSysCache(PROCOID, @@ -1061,39 +1128,35 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) elog(ERROR, "cache lookup failed for function %u", fn_oid); procStruct = (Form_pg_proc) GETSTRUCT(procTup); - /************************************************************ - * Build our internal proc name from the functions Oid - ************************************************************/ - if (!is_trigger) - snprintf(internal_proname, sizeof(internal_proname), - "__PLTcl_proc_%u", fn_oid); - else - snprintf(internal_proname, sizeof(internal_proname), - "__PLTcl_proc_%u_trigger_%u", fn_oid, tgreloid); + /* Try to find function in pltcl_proc_htab */ + proc_key.proc_id = fn_oid; + proc_key.trig_id = tgreloid; + proc_key.user_id = pltrusted ? GetUserId() : InvalidOid; - /************************************************************ - * Lookup the internal proc name in the hashtable - ************************************************************/ - hashent = Tcl_FindHashEntry(pltcl_proc_hash, internal_proname); + proc_ptr = hash_search(pltcl_proc_htab, &proc_key, + HASH_ENTER, + &found); + if (!found) + proc_ptr->proc_ptr = NULL; + + prodesc = proc_ptr->proc_ptr; /************************************************************ * If it's present, must check whether it's still up to date. * This is needed because CREATE OR REPLACE FUNCTION can modify the * function's pg_proc entry without changing its OID. ************************************************************/ - if (hashent != NULL) + if (prodesc != NULL) { bool uptodate; - prodesc = (pltcl_proc_desc *) Tcl_GetHashValue(hashent); - uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data)); if (!uptodate) { - Tcl_DeleteHashEntry(hashent); - hashent = NULL; + proc_ptr->proc_ptr = NULL; + prodesc = NULL; } } @@ -1105,17 +1168,30 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) * * Then we load the procedure into the Tcl interpreter. ************************************************************/ - if (hashent == NULL) + if (prodesc == NULL) { - HeapTuple langTup; + bool is_trigger = OidIsValid(tgreloid); + char internal_proname[128]; HeapTuple typeTup; - Form_pg_language langStruct; Form_pg_type typeStruct; Tcl_DString proc_internal_def; Tcl_DString proc_internal_body; char proc_internal_args[4096]; char *proc_source; char buf[512]; + Tcl_Interp *interp; + int i; + int tcl_rc; + + /************************************************************ + * Build our internal proc name from the functions Oid + trigger Oid + ************************************************************/ + if (!is_trigger) + snprintf(internal_proname, sizeof(internal_proname), + "__PLTcl_proc_%u", fn_oid); + else + snprintf(internal_proname, sizeof(internal_proname), + "__PLTcl_proc_%u_trigger_%u", fn_oid, tgreloid); /************************************************************ * Allocate a new procedure description block @@ -1127,27 +1203,19 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) errmsg("out of memory"))); MemSet(prodesc, 0, sizeof(pltcl_proc_desc)); prodesc->proname = strdup(internal_proname); + if (prodesc->proname == NULL) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data); + prodesc->lanpltrusted = pltrusted; /************************************************************ - * Lookup the pg_language tuple by Oid + * Identify the interpreter to use for the function ************************************************************/ - langTup = SearchSysCache(LANGOID, - ObjectIdGetDatum(procStruct->prolang), - 0, 0, 0); - if (!HeapTupleIsValid(langTup)) - { - free(prodesc->proname); - free(prodesc); - elog(ERROR, "cache lookup failed for language %u", - procStruct->prolang); - } - langStruct = (Form_pg_language) GETSTRUCT(langTup); - prodesc->lanpltrusted = langStruct->lanpltrusted; - ReleaseSysCache(langTup); - - interp = pltcl_fetch_interp(prodesc->lanpltrusted); + prodesc->interp_desc = pltcl_fetch_interp(prodesc->lanpltrusted); + interp = prodesc->interp_desc->interp; /************************************************************ * Get the required information for input conversion of the @@ -1344,11 +1412,12 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) } /************************************************************ - * Add the proc description block to the hashtable + * Add the proc description block to the hashtable. Note we do not + * attempt to free any previously existing prodesc block. This is + * annoying, but necessary since there could be active calls using + * the old prodesc. ************************************************************/ - hashent = Tcl_CreateHashEntry(pltcl_proc_hash, - prodesc->proname, &hashnew); - Tcl_SetHashValue(hashent, (ClientData) prodesc); + proc_ptr->proc_ptr = prodesc; } ReleaseSysCache(procTup); @@ -1952,10 +2021,7 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, * Insert a hashtable entry for the plan and return * the key to the caller ************************************************************/ - if (interp == pltcl_norm_interp) - query_hash = pltcl_norm_query_hash; - else - query_hash = pltcl_safe_query_hash; + query_hash = &pltcl_current_prodesc->interp_desc->query_hash; memcpy(&Warn_restart, &save_restart, sizeof(Warn_restart)); @@ -2069,10 +2135,7 @@ pltcl_SPI_execp(ClientData cdata, Tcl_Interp *interp, /************************************************************ * Get the prepared plan descriptor by its key ************************************************************/ - if (interp == pltcl_norm_interp) - query_hash = pltcl_norm_query_hash; - else - query_hash = pltcl_safe_query_hash; + query_hash = &pltcl_current_prodesc->interp_desc->query_hash; hashent = Tcl_FindHashEntry(query_hash, argv[i++]); if (hashent == NULL)