diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml index 270c2f1514..decac45887 100644 --- a/doc/src/sgml/pltcl.sgml +++ b/doc/src/sgml/pltcl.sgml @@ -1,4 +1,4 @@ - + PL/Tcl - Tcl Procedural Language @@ -690,8 +690,10 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab It recognizes a special table, pltcl_modules, which is presumed to contain modules of Tcl code. If this table exists, the module unknown is fetched from the table - and loaded into the Tcl interpreter immediately after creating - the interpreter. + 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.) While the unknown module could actually contain any @@ -718,7 +720,11 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab The tables pltcl_modules and pltcl_modfuncs must be readable by all, but it is wise to make them owned and - writable only by the database administrator. + writable only by the database administrator. As a security + precaution, PL/Tcl will ignore pltcl_modules (and thus, + not attempt to load the unknown module) unless it is + owned by a superuser. But update privileges on this table can be + granted to other users, if you trust them sufficiently. diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index 8fc2d41122..2e30855a89 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -2,7 +2,7 @@ * pltcl.c - PostgreSQL support for Tcl as * procedural language (PL) * - * $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.108.2.2 2010/01/25 01:58:33 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.108.2.3 2010/05/13 18:29:31 tgl Exp $ * **********************************************************************/ @@ -19,15 +19,18 @@ #endif #include "access/heapam.h" +#include "catalog/namespace.h" #include "catalog/pg_language.h" #include "catalog/pg_proc.h" #include "commands/trigger.h" #include "executor/spi.h" #include "fmgr.h" +#include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_type.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" @@ -112,7 +115,8 @@ typedef struct pltcl_query_desc * Global data **********************************************************************/ static bool pltcl_pm_init_done = false; -static bool pltcl_be_init_done = false; +static bool pltcl_be_norm_init_done = false; +static bool pltcl_be_safe_init_done = false; static Tcl_Interp *pltcl_hold_interp = NULL; static Tcl_Interp *pltcl_norm_interp = NULL; static Tcl_Interp *pltcl_safe_interp = NULL; @@ -131,8 +135,8 @@ Datum pltcl_call_handler(PG_FUNCTION_ARGS); Datum pltclu_call_handler(PG_FUNCTION_ARGS); void _PG_init(void); -static void pltcl_init_all(void); 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); static Datum pltcl_func_handler(PG_FUNCTION_ARGS); @@ -243,33 +247,12 @@ _PG_init(void) pltcl_pm_init_done = true; } -/********************************************************************** - * pltcl_init_all() - Initialize all - * - * This does initialization that can't be done in the postmaster, and - * hence is not safe to do at library load time. - **********************************************************************/ -static void -pltcl_init_all(void) -{ - /************************************************************ - * Try to load the unknown procedure from pltcl_modules - ************************************************************/ - if (!pltcl_be_init_done) - { - if (SPI_connect() != SPI_OK_CONNECT) - elog(ERROR, "SPI_connect failed"); - pltcl_init_load_unknown(pltcl_norm_interp); - pltcl_init_load_unknown(pltcl_safe_interp); - if (SPI_finish() != SPI_OK_FINISH) - elog(ERROR, "SPI_finish failed"); - pltcl_be_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. **********************************************************************/ static void pltcl_init_interp(Tcl_Interp *interp) @@ -296,6 +279,42 @@ pltcl_init_interp(Tcl_Interp *interp) pltcl_SPI_lastoid, NULL, NULL); } +/********************************************************************** + * 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 * +pltcl_fetch_interp(bool pltrusted) +{ + Tcl_Interp *interp; + + /* On first use, we try to load the unknown procedure from pltcl_modules */ + if (pltrusted) + { + interp = pltcl_safe_interp; + if (!pltcl_be_safe_init_done) + { + pltcl_init_load_unknown(interp); + pltcl_be_safe_init_done = true; + } + } + else + { + interp = pltcl_norm_interp; + if (!pltcl_be_norm_init_done) + { + pltcl_init_load_unknown(interp); + pltcl_be_norm_init_done = true; + } + } + + return interp; +} /********************************************************************** * pltcl_init_load_unknown() - Load the unknown procedure from @@ -304,6 +323,12 @@ pltcl_init_interp(Tcl_Interp *interp) static void pltcl_init_load_unknown(Tcl_Interp *interp) { + Oid relOid; + Relation pmrel; + char *pmrelname, + *nspname; + char *buf; + int buflen; int spi_rc; int tcl_rc; Tcl_DString unknown_src; @@ -313,47 +338,87 @@ pltcl_init_load_unknown(Tcl_Interp *interp) /************************************************************ * Check if table pltcl_modules exists + * + * We allow the table to be found anywhere in the search_path. + * This is for backwards compatibility. To ensure that the table + * is trustworthy, we require it to be owned by a superuser. + * + * this next bit of code is the same as try_relation_openrv(), + * which only exists in 8.4 and up. ************************************************************/ - spi_rc = SPI_execute("select 1 from pg_catalog.pg_class " - "where relname = 'pltcl_modules'", - false, 1); - SPI_freetuptable(SPI_tuptable); - if (spi_rc != SPI_OK_SELECT) - elog(ERROR, "select from pg_class failed"); - if (SPI_processed == 0) + + /* Check for shared-cache-inval messages */ + AcceptInvalidationMessages(); + + /* Look up the appropriate relation using namespace search */ + relOid = RangeVarGetRelid(makeRangeVar(NULL, "pltcl_modules"), true); + + /* Drop out on not-found */ + if (!OidIsValid(relOid)) return; - /************************************************************ - * Read all the row's from it where modname = 'unknown' in - * the order of modseq - ************************************************************/ - Tcl_DStringInit(&unknown_src); + /* Let relation_open do the rest */ + pmrel = relation_open(relOid, AccessShareLock); - spi_rc = SPI_execute("select modseq, modsrc from pltcl_modules " - "where modname = 'unknown' " - "order by modseq", - false, 0); + if (pmrel == NULL) + return; + /* must be table or view, else ignore */ + if (!(pmrel->rd_rel->relkind == RELKIND_RELATION || + pmrel->rd_rel->relkind == RELKIND_VIEW)) + { + relation_close(pmrel, AccessShareLock); + return; + } + /* must be owned by superuser, else ignore */ + if (!superuser_arg(pmrel->rd_rel->relowner)) + { + relation_close(pmrel, AccessShareLock); + return; + } + /* get fully qualified table name for use in select command */ + nspname = get_namespace_name(RelationGetNamespace(pmrel)); + if (!nspname) + elog(ERROR, "cache lookup failed for namespace %u", + RelationGetNamespace(pmrel)); + pmrelname = quote_qualified_identifier(nspname, + RelationGetRelationName(pmrel)); + + /************************************************************ + * Read all the rows from it where modname = 'unknown', + * in the order of modseq + ************************************************************/ + buflen = strlen(pmrelname) + 100; + buf = (char *) palloc(buflen); + snprintf(buf, buflen, + "select modsrc from %s where modname = 'unknown' order by modseq", + pmrelname); + + spi_rc = SPI_execute(buf, false, 0); if (spi_rc != SPI_OK_SELECT) elog(ERROR, "select from pltcl_modules failed"); + pfree(buf); + /************************************************************ * If there's nothing, module unknown doesn't exist ************************************************************/ if (SPI_processed == 0) { - Tcl_DStringFree(&unknown_src); SPI_freetuptable(SPI_tuptable); elog(WARNING, "module \"unknown\" not found in pltcl_modules"); + relation_close(pmrel, AccessShareLock); return; } /************************************************************ - * There is a module named unknown. Resemble the + * There is a module named unknown. Reassemble the * source from the modsrc attributes and evaluate * it in the Tcl interpreter ************************************************************/ fno = SPI_fnumber(SPI_tuptable->tupdesc, "modsrc"); + Tcl_DStringInit(&unknown_src); + for (i = 0; i < SPI_processed; i++) { part = SPI_getvalue(SPI_tuptable->vals[i], @@ -367,8 +432,19 @@ pltcl_init_load_unknown(Tcl_Interp *interp) } } tcl_rc = Tcl_GlobalEval(interp, Tcl_DStringValue(&unknown_src)); + Tcl_DStringFree(&unknown_src); SPI_freetuptable(SPI_tuptable); + + if (tcl_rc != TCL_OK) + { + UTF_BEGIN; + elog(ERROR, "could not load module \"unknown\": %s", + UTF_U2E(Tcl_GetStringResult(interp))); + UTF_END; + } + + relation_close(pmrel, AccessShareLock); } @@ -389,11 +465,6 @@ pltcl_call_handler(PG_FUNCTION_ARGS) FunctionCallInfo save_fcinfo; pltcl_proc_desc *save_prodesc; - /* - * Initialize interpreters if first time through - */ - pltcl_init_all(); - /* * Ensure that static pointers are saved/restored properly */ @@ -467,10 +538,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS) pltcl_current_prodesc = prodesc; - if (prodesc->lanpltrusted) - interp = pltcl_safe_interp; - else - interp = pltcl_norm_interp; + interp = pltcl_fetch_interp(prodesc->lanpltrusted); /************************************************************ * Create the tcl command to call the internal @@ -628,10 +696,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS) pltcl_current_prodesc = prodesc; - if (prodesc->lanpltrusted) - interp = pltcl_safe_interp; - else - interp = pltcl_norm_interp; + interp = pltcl_fetch_interp(prodesc->lanpltrusted); tupdesc = trigdata->tg_relation->rd_att; @@ -1060,10 +1125,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) prodesc->lanpltrusted = langStruct->lanpltrusted; ReleaseSysCache(langTup); - if (prodesc->lanpltrusted) - interp = pltcl_safe_interp; - else - interp = pltcl_norm_interp; + interp = pltcl_fetch_interp(prodesc->lanpltrusted); /************************************************************ * Get the required information for input conversion of the