Prevent PL/Tcl from loading the "unknown" module from pltcl_modules unless

that is a regular table or view owned by a superuser.  This prevents a
trojan horse attack whereby any unprivileged SQL user could create such a
table and insert code into it that would then get executed in other users'
sessions whenever they call pltcl functions.

Worse yet, because the code was automatically loaded into both the "normal"
and "safe" interpreters at first use, the attacker could execute unrestricted
Tcl code in the "normal" interpreter without there being any pltclu functions
anywhere, or indeed anyone else using pltcl at all: installing pltcl is
sufficient to open the hole.  Change the initialization logic so that the
"unknown" code is only loaded into an interpreter when the interpreter is
first really used.  (That doesn't add any additional security in this
particular context, but it seems a prudent change, and anyway the former
behavior violated the principle of least astonishment.)

Security: CVE-2010-1170
This commit is contained in:
Tom Lane 2010-05-13 18:29:31 +00:00
parent 64a42a2af8
commit 650d950efc
2 changed files with 135 additions and 67 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.42.2.1 2007/01/30 22:29:40 momjian Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.42.2.2 2010/05/13 18:29:31 tgl Exp $ -->
<chapter id="pltcl"> <chapter id="pltcl">
<title>PL/Tcl - Tcl Procedural Language</title> <title>PL/Tcl - Tcl Procedural Language</title>
@ -690,8 +690,10 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
It recognizes a special table, <literal>pltcl_modules</>, which It recognizes a special table, <literal>pltcl_modules</>, which
is presumed to contain modules of Tcl code. If this table is presumed to contain modules of Tcl code. If this table
exists, the module <literal>unknown</> is fetched from the table exists, the module <literal>unknown</> is fetched from the table
and loaded into the Tcl interpreter immediately after creating and loaded into the Tcl interpreter immediately before the first
the interpreter. 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.)
</para> </para>
<para> <para>
While the <literal>unknown</> module could actually contain any While the <literal>unknown</> module could actually contain any
@ -718,7 +720,11 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
<para> <para>
The tables <literal>pltcl_modules</> and <literal>pltcl_modfuncs</> The tables <literal>pltcl_modules</> and <literal>pltcl_modfuncs</>
must be readable by all, but it is wise to make them owned and 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 <literal>pltcl_modules</> (and thus,
not attempt to load the <literal>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.
</para> </para>
</sect1> </sect1>

View File

@ -2,7 +2,7 @@
* pltcl.c - PostgreSQL support for Tcl as * pltcl.c - PostgreSQL support for Tcl as
* procedural language (PL) * 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 #endif
#include "access/heapam.h" #include "access/heapam.h"
#include "catalog/namespace.h"
#include "catalog/pg_language.h" #include "catalog/pg_language.h"
#include "catalog/pg_proc.h" #include "catalog/pg_proc.h"
#include "commands/trigger.h" #include "commands/trigger.h"
#include "executor/spi.h" #include "executor/spi.h"
#include "fmgr.h" #include "fmgr.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
#include "parser/parse_type.h" #include "parser/parse_type.h"
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/inval.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/memutils.h" #include "utils/memutils.h"
#include "utils/syscache.h" #include "utils/syscache.h"
@ -112,7 +115,8 @@ typedef struct pltcl_query_desc
* Global data * Global data
**********************************************************************/ **********************************************************************/
static bool pltcl_pm_init_done = false; 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_hold_interp = NULL;
static Tcl_Interp *pltcl_norm_interp = NULL; static Tcl_Interp *pltcl_norm_interp = NULL;
static Tcl_Interp *pltcl_safe_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); Datum pltclu_call_handler(PG_FUNCTION_ARGS);
void _PG_init(void); void _PG_init(void);
static void pltcl_init_all(void);
static void pltcl_init_interp(Tcl_Interp *interp); 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 void pltcl_init_load_unknown(Tcl_Interp *interp);
static Datum pltcl_func_handler(PG_FUNCTION_ARGS); static Datum pltcl_func_handler(PG_FUNCTION_ARGS);
@ -243,33 +247,12 @@ _PG_init(void)
pltcl_pm_init_done = true; 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 * 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 static void
pltcl_init_interp(Tcl_Interp *interp) pltcl_init_interp(Tcl_Interp *interp)
@ -296,6 +279,42 @@ pltcl_init_interp(Tcl_Interp *interp)
pltcl_SPI_lastoid, NULL, NULL); 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 * pltcl_init_load_unknown() - Load the unknown procedure from
@ -304,6 +323,12 @@ pltcl_init_interp(Tcl_Interp *interp)
static void static void
pltcl_init_load_unknown(Tcl_Interp *interp) pltcl_init_load_unknown(Tcl_Interp *interp)
{ {
Oid relOid;
Relation pmrel;
char *pmrelname,
*nspname;
char *buf;
int buflen;
int spi_rc; int spi_rc;
int tcl_rc; int tcl_rc;
Tcl_DString unknown_src; Tcl_DString unknown_src;
@ -313,47 +338,87 @@ pltcl_init_load_unknown(Tcl_Interp *interp)
/************************************************************ /************************************************************
* Check if table pltcl_modules exists * 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'", /* Check for shared-cache-inval messages */
false, 1); AcceptInvalidationMessages();
SPI_freetuptable(SPI_tuptable);
if (spi_rc != SPI_OK_SELECT) /* Look up the appropriate relation using namespace search */
elog(ERROR, "select from pg_class failed"); relOid = RangeVarGetRelid(makeRangeVar(NULL, "pltcl_modules"), true);
if (SPI_processed == 0)
/* Drop out on not-found */
if (!OidIsValid(relOid))
return; return;
/************************************************************ /* Let relation_open do the rest */
* Read all the row's from it where modname = 'unknown' in pmrel = relation_open(relOid, AccessShareLock);
* the order of modseq
************************************************************/
Tcl_DStringInit(&unknown_src);
spi_rc = SPI_execute("select modseq, modsrc from pltcl_modules " if (pmrel == NULL)
"where modname = 'unknown' " return;
"order by modseq", /* must be table or view, else ignore */
false, 0); 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) if (spi_rc != SPI_OK_SELECT)
elog(ERROR, "select from pltcl_modules failed"); elog(ERROR, "select from pltcl_modules failed");
pfree(buf);
/************************************************************ /************************************************************
* If there's nothing, module unknown doesn't exist * If there's nothing, module unknown doesn't exist
************************************************************/ ************************************************************/
if (SPI_processed == 0) if (SPI_processed == 0)
{ {
Tcl_DStringFree(&unknown_src);
SPI_freetuptable(SPI_tuptable); SPI_freetuptable(SPI_tuptable);
elog(WARNING, "module \"unknown\" not found in pltcl_modules"); elog(WARNING, "module \"unknown\" not found in pltcl_modules");
relation_close(pmrel, AccessShareLock);
return; return;
} }
/************************************************************ /************************************************************
* There is a module named unknown. Resemble the * There is a module named unknown. Reassemble the
* source from the modsrc attributes and evaluate * source from the modsrc attributes and evaluate
* it in the Tcl interpreter * it in the Tcl interpreter
************************************************************/ ************************************************************/
fno = SPI_fnumber(SPI_tuptable->tupdesc, "modsrc"); fno = SPI_fnumber(SPI_tuptable->tupdesc, "modsrc");
Tcl_DStringInit(&unknown_src);
for (i = 0; i < SPI_processed; i++) for (i = 0; i < SPI_processed; i++)
{ {
part = SPI_getvalue(SPI_tuptable->vals[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_rc = Tcl_GlobalEval(interp, Tcl_DStringValue(&unknown_src));
Tcl_DStringFree(&unknown_src); Tcl_DStringFree(&unknown_src);
SPI_freetuptable(SPI_tuptable); 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; FunctionCallInfo save_fcinfo;
pltcl_proc_desc *save_prodesc; pltcl_proc_desc *save_prodesc;
/*
* Initialize interpreters if first time through
*/
pltcl_init_all();
/* /*
* Ensure that static pointers are saved/restored properly * Ensure that static pointers are saved/restored properly
*/ */
@ -467,10 +538,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS)
pltcl_current_prodesc = prodesc; pltcl_current_prodesc = prodesc;
if (prodesc->lanpltrusted) interp = pltcl_fetch_interp(prodesc->lanpltrusted);
interp = pltcl_safe_interp;
else
interp = pltcl_norm_interp;
/************************************************************ /************************************************************
* Create the tcl command to call the internal * Create the tcl command to call the internal
@ -628,10 +696,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS)
pltcl_current_prodesc = prodesc; pltcl_current_prodesc = prodesc;
if (prodesc->lanpltrusted) interp = pltcl_fetch_interp(prodesc->lanpltrusted);
interp = pltcl_safe_interp;
else
interp = pltcl_norm_interp;
tupdesc = trigdata->tg_relation->rd_att; tupdesc = trigdata->tg_relation->rd_att;
@ -1060,10 +1125,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
prodesc->lanpltrusted = langStruct->lanpltrusted; prodesc->lanpltrusted = langStruct->lanpltrusted;
ReleaseSysCache(langTup); ReleaseSysCache(langTup);
if (prodesc->lanpltrusted) interp = pltcl_fetch_interp(prodesc->lanpltrusted);
interp = pltcl_safe_interp;
else
interp = pltcl_norm_interp;
/************************************************************ /************************************************************
* Get the required information for input conversion of the * Get the required information for input conversion of the