Skip to content

Commit 9ead05b

Browse files
committed
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
1 parent 1f474d2 commit 9ead05b

File tree

2 files changed

+114
-65
lines changed

2 files changed

+114
-65
lines changed

doc/src/sgml/pltcl.sgml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- $PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.49 2010/04/03 07:22:55 petere Exp $ -->
1+
<!-- $PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.50 2010/05/13 18:29:12 tgl Exp $ -->
22

33
<chapter id="pltcl">
44
<title>PL/Tcl - Tcl Procedural Language</title>
@@ -689,8 +689,10 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
689689
It recognizes a special table, <literal>pltcl_modules</>, which
690690
is presumed to contain modules of Tcl code. If this table
691691
exists, the module <literal>unknown</> is fetched from the table
692-
and loaded into the Tcl interpreter immediately after creating
693-
the interpreter.
692+
and loaded into the Tcl interpreter immediately before the first
693+
execution of a PL/Tcl function in a database session. (This
694+
happens separately for PL/Tcl and PL/TclU, if both are used,
695+
because separate interpreters are used for the two languages.)
694696
</para>
695697
<para>
696698
While the <literal>unknown</> module could actually contain any
@@ -717,7 +719,11 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
717719
<para>
718720
The tables <literal>pltcl_modules</> and <literal>pltcl_modfuncs</>
719721
must be readable by all, but it is wise to make them owned and
720-
writable only by the database administrator.
722+
writable only by the database administrator. As a security
723+
precaution, PL/Tcl will ignore <literal>pltcl_modules</> (and thus,
724+
not attempt to load the <literal>unknown</> module) unless it is
725+
owned by a superuser. But update privileges on this table can be
726+
granted to other users, if you trust them sufficiently.
721727
</para>
722728
</sect1>
723729

src/pl/tcl/pltcl.c

Lines changed: 104 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* pltcl.c - PostgreSQL support for Tcl as
33
* procedural language (PL)
44
*
5-
* $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.132 2010/02/26 02:01:37 momjian Exp $
5+
* $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.133 2010/05/13 18:29:12 tgl Exp $
66
*
77
**********************************************************************/
88

@@ -120,7 +120,8 @@ typedef struct pltcl_query_desc
120120
* Global data
121121
**********************************************************************/
122122
static bool pltcl_pm_init_done = false;
123-
static bool pltcl_be_init_done = false;
123+
static bool pltcl_be_norm_init_done = false;
124+
static bool pltcl_be_safe_init_done = false;
124125
static Tcl_Interp *pltcl_hold_interp = NULL;
125126
static Tcl_Interp *pltcl_norm_interp = NULL;
126127
static Tcl_Interp *pltcl_safe_interp = NULL;
@@ -139,8 +140,8 @@ Datum pltcl_call_handler(PG_FUNCTION_ARGS);
139140
Datum pltclu_call_handler(PG_FUNCTION_ARGS);
140141
void _PG_init(void);
141142

142-
static void pltcl_init_all(void);
143143
static void pltcl_init_interp(Tcl_Interp *interp);
144+
static Tcl_Interp *pltcl_fetch_interp(bool pltrusted);
144145
static void pltcl_init_load_unknown(Tcl_Interp *interp);
145146

146147
static Datum pltcl_func_handler(PG_FUNCTION_ARGS);
@@ -334,33 +335,12 @@ _PG_init(void)
334335
pltcl_pm_init_done = true;
335336
}
336337

337-
/**********************************************************************
338-
* pltcl_init_all() - Initialize all
339-
*
340-
* This does initialization that can't be done in the postmaster, and
341-
* hence is not safe to do at library load time.
342-
**********************************************************************/
343-
static void
344-
pltcl_init_all(void)
345-
{
346-
/************************************************************
347-
* Try to load the unknown procedure from pltcl_modules
348-
************************************************************/
349-
if (!pltcl_be_init_done)
350-
{
351-
if (SPI_connect() != SPI_OK_CONNECT)
352-
elog(ERROR, "SPI_connect failed");
353-
pltcl_init_load_unknown(pltcl_norm_interp);
354-
pltcl_init_load_unknown(pltcl_safe_interp);
355-
if (SPI_finish() != SPI_OK_FINISH)
356-
elog(ERROR, "SPI_finish failed");
357-
pltcl_be_init_done = true;
358-
}
359-
}
360-
361-
362338
/**********************************************************************
363339
* pltcl_init_interp() - initialize a Tcl interpreter
340+
*
341+
* The work done here must be safe to do in the postmaster process,
342+
* in case the pltcl library is preloaded in the postmaster. Note
343+
* that this is applied separately to the "normal" and "safe" interpreters.
364344
**********************************************************************/
365345
static void
366346
pltcl_init_interp(Tcl_Interp *interp)
@@ -387,6 +367,42 @@ pltcl_init_interp(Tcl_Interp *interp)
387367
pltcl_SPI_lastoid, NULL, NULL);
388368
}
389369

370+
/**********************************************************************
371+
* pltcl_fetch_interp() - fetch the Tcl interpreter to use for a function
372+
*
373+
* This also takes care of any on-first-use initialization required.
374+
* The initialization work done here can't be done in the postmaster, and
375+
* hence is not safe to do at library load time, because it may invoke
376+
* arbitrary user-defined code.
377+
* Note: we assume caller has already connected to SPI.
378+
**********************************************************************/
379+
static Tcl_Interp *
380+
pltcl_fetch_interp(bool pltrusted)
381+
{
382+
Tcl_Interp *interp;
383+
384+
/* On first use, we try to load the unknown procedure from pltcl_modules */
385+
if (pltrusted)
386+
{
387+
interp = pltcl_safe_interp;
388+
if (!pltcl_be_safe_init_done)
389+
{
390+
pltcl_init_load_unknown(interp);
391+
pltcl_be_safe_init_done = true;
392+
}
393+
}
394+
else
395+
{
396+
interp = pltcl_norm_interp;
397+
if (!pltcl_be_norm_init_done)
398+
{
399+
pltcl_init_load_unknown(interp);
400+
pltcl_be_norm_init_done = true;
401+
}
402+
}
403+
404+
return interp;
405+
}
390406

391407
/**********************************************************************
392408
* pltcl_init_load_unknown() - Load the unknown procedure from
@@ -395,6 +411,11 @@ pltcl_init_interp(Tcl_Interp *interp)
395411
static void
396412
pltcl_init_load_unknown(Tcl_Interp *interp)
397413
{
414+
Relation pmrel;
415+
char *pmrelname,
416+
*nspname;
417+
char *buf;
418+
int buflen;
398419
int spi_rc;
399420
int tcl_rc;
400421
Tcl_DString unknown_src;
@@ -404,47 +425,72 @@ pltcl_init_load_unknown(Tcl_Interp *interp)
404425

405426
/************************************************************
406427
* Check if table pltcl_modules exists
428+
*
429+
* We allow the table to be found anywhere in the search_path.
430+
* This is for backwards compatibility. To ensure that the table
431+
* is trustworthy, we require it to be owned by a superuser.
407432
************************************************************/
408-
spi_rc = SPI_execute("select 1 from pg_catalog.pg_class "
409-
"where relname = 'pltcl_modules'",
410-
false, 1);
411-
SPI_freetuptable(SPI_tuptable);
412-
if (spi_rc != SPI_OK_SELECT)
413-
elog(ERROR, "select from pg_class failed");
414-
if (SPI_processed == 0)
433+
pmrel = try_relation_openrv(makeRangeVar(NULL, "pltcl_modules", -1),
434+
AccessShareLock);
435+
if (pmrel == NULL)
436+
return;
437+
/* must be table or view, else ignore */
438+
if (!(pmrel->rd_rel->relkind == RELKIND_RELATION ||
439+
pmrel->rd_rel->relkind == RELKIND_VIEW))
440+
{
441+
relation_close(pmrel, AccessShareLock);
442+
return;
443+
}
444+
/* must be owned by superuser, else ignore */
445+
if (!superuser_arg(pmrel->rd_rel->relowner))
446+
{
447+
relation_close(pmrel, AccessShareLock);
415448
return;
449+
}
450+
/* get fully qualified table name for use in select command */
451+
nspname = get_namespace_name(RelationGetNamespace(pmrel));
452+
if (!nspname)
453+
elog(ERROR, "cache lookup failed for namespace %u",
454+
RelationGetNamespace(pmrel));
455+
pmrelname = quote_qualified_identifier(nspname,
456+
RelationGetRelationName(pmrel));
416457

417458
/************************************************************
418-
* Read all the row's from it where modname = 'unknown' in
419-
* the order of modseq
459+
* Read all the rows from it where modname = 'unknown',
460+
* in the order of modseq
420461
************************************************************/
421-
Tcl_DStringInit(&unknown_src);
462+
buflen = strlen(pmrelname) + 100;
463+
buf = (char *) palloc(buflen);
464+
snprintf(buf, buflen,
465+
"select modsrc from %s where modname = 'unknown' order by modseq",
466+
pmrelname);
422467

423-
spi_rc = SPI_execute("select modseq, modsrc from pltcl_modules "
424-
"where modname = 'unknown' "
425-
"order by modseq",
426-
false, 0);
468+
spi_rc = SPI_execute(buf, false, 0);
427469
if (spi_rc != SPI_OK_SELECT)
428470
elog(ERROR, "select from pltcl_modules failed");
429471

472+
pfree(buf);
473+
430474
/************************************************************
431475
* If there's nothing, module unknown doesn't exist
432476
************************************************************/
433477
if (SPI_processed == 0)
434478
{
435-
Tcl_DStringFree(&unknown_src);
436479
SPI_freetuptable(SPI_tuptable);
437480
elog(WARNING, "module \"unknown\" not found in pltcl_modules");
481+
relation_close(pmrel, AccessShareLock);
438482
return;
439483
}
440484

441485
/************************************************************
442-
* There is a module named unknown. Resemble the
486+
* There is a module named unknown. Reassemble the
443487
* source from the modsrc attributes and evaluate
444488
* it in the Tcl interpreter
445489
************************************************************/
446490
fno = SPI_fnumber(SPI_tuptable->tupdesc, "modsrc");
447491

492+
Tcl_DStringInit(&unknown_src);
493+
448494
for (i = 0; i < SPI_processed; i++)
449495
{
450496
part = SPI_getvalue(SPI_tuptable->vals[i],
@@ -458,8 +504,19 @@ pltcl_init_load_unknown(Tcl_Interp *interp)
458504
}
459505
}
460506
tcl_rc = Tcl_GlobalEval(interp, Tcl_DStringValue(&unknown_src));
507+
461508
Tcl_DStringFree(&unknown_src);
462509
SPI_freetuptable(SPI_tuptable);
510+
511+
if (tcl_rc != TCL_OK)
512+
{
513+
UTF_BEGIN;
514+
elog(ERROR, "could not load module \"unknown\": %s",
515+
UTF_U2E(Tcl_GetStringResult(interp)));
516+
UTF_END;
517+
}
518+
519+
relation_close(pmrel, AccessShareLock);
463520
}
464521

465522

@@ -480,11 +537,6 @@ pltcl_call_handler(PG_FUNCTION_ARGS)
480537
FunctionCallInfo save_fcinfo;
481538
pltcl_proc_desc *save_prodesc;
482539

483-
/*
484-
* Initialize interpreters if first time through
485-
*/
486-
pltcl_init_all();
487-
488540
/*
489541
* Ensure that static pointers are saved/restored properly
490542
*/
@@ -558,10 +610,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS)
558610

559611
pltcl_current_prodesc = prodesc;
560612

561-
if (prodesc->lanpltrusted)
562-
interp = pltcl_safe_interp;
563-
else
564-
interp = pltcl_norm_interp;
613+
interp = pltcl_fetch_interp(prodesc->lanpltrusted);
565614

566615
/************************************************************
567616
* Create the tcl command to call the internal
@@ -719,10 +768,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS)
719768

720769
pltcl_current_prodesc = prodesc;
721770

722-
if (prodesc->lanpltrusted)
723-
interp = pltcl_safe_interp;
724-
else
725-
interp = pltcl_norm_interp;
771+
interp = pltcl_fetch_interp(prodesc->lanpltrusted);
726772

727773
tupdesc = trigdata->tg_relation->rd_att;
728774

@@ -1152,10 +1198,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
11521198
prodesc->lanpltrusted = langStruct->lanpltrusted;
11531199
ReleaseSysCache(langTup);
11541200

1155-
if (prodesc->lanpltrusted)
1156-
interp = pltcl_safe_interp;
1157-
else
1158-
interp = pltcl_norm_interp;
1201+
interp = pltcl_fetch_interp(prodesc->lanpltrusted);
11591202

11601203
/************************************************************
11611204
* Get the required information for input conversion of the

0 commit comments

Comments
 (0)