Skip to content

Commit 4aadfba

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 60028fd commit 4aadfba

File tree

2 files changed

+142
-70
lines changed

2 files changed

+142
-70
lines changed

doc/src/sgml/pltcl.sgml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
$Header: /cvsroot/pgsql/doc/src/sgml/pltcl.sgml,v 2.26.2.2 2004/01/24 23:06:41 tgl Exp $
2+
$Header: /cvsroot/pgsql/doc/src/sgml/pltcl.sgml,v 2.26.2.3 2010/05/13 18:29:54 tgl Exp $
33
-->
44

55
<chapter id="pltcl">
@@ -646,11 +646,13 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
646646
It recognizes a special table, <literal>pltcl_modules</>, which
647647
is presumed to contain modules of Tcl code. If this table
648648
exists, the module <literal>unknown</> is fetched from the table
649-
and loaded into the Tcl interpreter immediately after creating
650-
the interpreter.
649+
and loaded into the Tcl interpreter immediately before the first
650+
execution of a PL/Tcl function in a database session. (This
651+
happens separately for PL/Tcl and PL/TclU, if both are used,
652+
because separate interpreters are used for the two languages.)
651653
</para>
652654
<para>
653-
While the <literal>unknown</> module could actually contain any
655+
While the <literal>unknown</> module could actually contain any
654656
initialization script you need, it normally defines a Tcl
655657
<function>unknown</> procedure that is invoked whenever Tcl does
656658
not recognize an invoked procedure name. <application>PL/Tcl</>'s standard version
@@ -663,18 +665,22 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
663665
is reasonably quick.
664666
</para>
665667
<para>
666-
The <productname>PostgreSQL</productname> distribution includes
668+
The <productname>PostgreSQL</productname> distribution includes
667669
support scripts to maintain these tables:
668670
<command>pltcl_loadmod</>, <command>pltcl_listmod</>,
669671
<command>pltcl_delmod</>, as well as source for the standard
670-
<literal>unknown</> module in <filename>share/unknown.pltcl</>. This module
672+
<literal>unknown</> module in <filename>share/unknown.pltcl</>. This module
671673
must be loaded
672674
into each database initially to support the autoloading mechanism.
673675
</para>
674676
<para>
675-
The tables <literal>pltcl_modules</> and <literal>pltcl_modfuncs</>
677+
The tables <literal>pltcl_modules</> and <literal>pltcl_modfuncs</>
676678
must be readable by all, but it is wise to make them owned and
677-
writable only by the database administrator.
679+
writable only by the database administrator. As a security
680+
precaution, PL/Tcl will ignore <literal>pltcl_modules</> (and thus,
681+
not attempt to load the <literal>unknown</> module) unless it is
682+
owned by a superuser. But update privileges on this table can be
683+
granted to other users, if you trust them sufficiently.
678684
</para>
679685
</sect1>
680686

src/pl/tcl/pltcl.c

Lines changed: 128 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
* ENHANCEMENTS, OR MODIFICATIONS.
3232
*
3333
* IDENTIFICATION
34-
* $Header: /cvsroot/pgsql/src/pl/tcl/pltcl.c,v 1.79.2.2 2010/01/25 01:58:57 tgl Exp $
34+
* $Header: /cvsroot/pgsql/src/pl/tcl/pltcl.c,v 1.79.2.3 2010/05/13 18:29:54 tgl Exp $
3535
*
3636
**********************************************************************/
3737

@@ -49,16 +49,20 @@
4949
#endif
5050

5151
#include "access/heapam.h"
52+
#include "catalog/namespace.h"
5253
#include "catalog/pg_language.h"
5354
#include "catalog/pg_proc.h"
5455
#include "catalog/pg_type.h"
5556
#include "commands/trigger.h"
5657
#include "executor/spi.h"
5758
#include "fmgr.h"
59+
#include "miscadmin.h"
5860
#include "nodes/makefuncs.h"
5961
#include "parser/parse_type.h"
6062
#include "tcop/tcopprot.h"
6163
#include "utils/builtins.h"
64+
#include "utils/inval.h"
65+
#include "utils/lsyscache.h"
6266
#include "utils/syscache.h"
6367

6468
#if defined(UNICODE_CONVERSION) && TCL_MAJOR_VERSION == 8 \
@@ -129,7 +133,8 @@ typedef struct pltcl_query_desc
129133
* Global data
130134
**********************************************************************/
131135
static bool pltcl_pm_init_done = false;
132-
static bool pltcl_be_init_done = false;
136+
static bool pltcl_be_norm_init_done = false;
137+
static bool pltcl_be_safe_init_done = false;
133138
static int pltcl_call_level = 0;
134139
static int pltcl_restart_in_progress = 0;
135140
static Tcl_Interp *pltcl_hold_interp = NULL;
@@ -143,9 +148,9 @@ static FunctionCallInfo pltcl_current_fcinfo = NULL;
143148
/**********************************************************************
144149
* Forward declarations
145150
**********************************************************************/
146-
static void pltcl_init_all(void);
147-
static void pltcl_init_interp(Tcl_Interp *interp);
148151

152+
static void pltcl_init_interp(Tcl_Interp *interp);
153+
static Tcl_Interp *pltcl_fetch_interp(bool pltrusted);
149154
static void pltcl_init_load_unknown(Tcl_Interp *interp);
150155

151156
Datum pltcl_call_handler(PG_FUNCTION_ARGS);
@@ -249,38 +254,12 @@ pltcl_init(void)
249254
pltcl_pm_init_done = true;
250255
}
251256

252-
/**********************************************************************
253-
* pltcl_init_all() - Initialize all
254-
**********************************************************************/
255-
static void
256-
pltcl_init_all(void)
257-
{
258-
/************************************************************
259-
* Execute postmaster-startup safe initialization
260-
************************************************************/
261-
if (!pltcl_pm_init_done)
262-
pltcl_init();
263-
264-
/************************************************************
265-
* Any other initialization that must be done each time a new
266-
* backend starts:
267-
* - Try to load the unknown procedure from pltcl_modules
268-
************************************************************/
269-
if (!pltcl_be_init_done)
270-
{
271-
if (SPI_connect() != SPI_OK_CONNECT)
272-
elog(ERROR, "SPI_connect failed");
273-
pltcl_init_load_unknown(pltcl_norm_interp);
274-
pltcl_init_load_unknown(pltcl_safe_interp);
275-
if (SPI_finish() != SPI_OK_FINISH)
276-
elog(ERROR, "SPI_finish failed");
277-
pltcl_be_init_done = true;
278-
}
279-
}
280-
281-
282257
/**********************************************************************
283258
* pltcl_init_interp() - initialize a Tcl interpreter
259+
*
260+
* The work done here must be safe to do in the postmaster process,
261+
* in case the pltcl library is preloaded in the postmaster. Note
262+
* that this is applied separately to the "normal" and "safe" interpreters.
284263
**********************************************************************/
285264
static void
286265
pltcl_init_interp(Tcl_Interp *interp)
@@ -307,6 +286,42 @@ pltcl_init_interp(Tcl_Interp *interp)
307286
pltcl_SPI_lastoid, NULL, NULL);
308287
}
309288

289+
/**********************************************************************
290+
* pltcl_fetch_interp() - fetch the Tcl interpreter to use for a function
291+
*
292+
* This also takes care of any on-first-use initialization required.
293+
* The initialization work done here can't be done in the postmaster, and
294+
* hence is not safe to do at library load time, because it may invoke
295+
* arbitrary user-defined code.
296+
* Note: we assume caller has already connected to SPI.
297+
**********************************************************************/
298+
static Tcl_Interp *
299+
pltcl_fetch_interp(bool pltrusted)
300+
{
301+
Tcl_Interp *interp;
302+
303+
/* On first use, we try to load the unknown procedure from pltcl_modules */
304+
if (pltrusted)
305+
{
306+
interp = pltcl_safe_interp;
307+
if (!pltcl_be_safe_init_done)
308+
{
309+
pltcl_init_load_unknown(interp);
310+
pltcl_be_safe_init_done = true;
311+
}
312+
}
313+
else
314+
{
315+
interp = pltcl_norm_interp;
316+
if (!pltcl_be_norm_init_done)
317+
{
318+
pltcl_init_load_unknown(interp);
319+
pltcl_be_norm_init_done = true;
320+
}
321+
}
322+
323+
return interp;
324+
}
310325

311326
/**********************************************************************
312327
* pltcl_init_load_unknown() - Load the unknown procedure from
@@ -315,6 +330,12 @@ pltcl_init_interp(Tcl_Interp *interp)
315330
static void
316331
pltcl_init_load_unknown(Tcl_Interp *interp)
317332
{
333+
Oid relOid;
334+
Relation pmrel;
335+
char *pmrelname,
336+
*nspname;
337+
char *buf;
338+
int buflen;
318339
int spi_rc;
319340
int tcl_rc;
320341
Tcl_DString unknown_src;
@@ -324,45 +345,87 @@ pltcl_init_load_unknown(Tcl_Interp *interp)
324345

325346
/************************************************************
326347
* Check if table pltcl_modules exists
348+
*
349+
* We allow the table to be found anywhere in the search_path.
350+
* This is for backwards compatibility. To ensure that the table
351+
* is trustworthy, we require it to be owned by a superuser.
352+
*
353+
* this next bit of code is the same as try_relation_openrv(),
354+
* which only exists in 8.4 and up.
327355
************************************************************/
328-
spi_rc = SPI_exec("select 1 from pg_catalog.pg_class "
329-
"where relname = 'pltcl_modules'", 1);
330-
SPI_freetuptable(SPI_tuptable);
331-
if (spi_rc != SPI_OK_SELECT)
332-
elog(ERROR, "select from pg_class failed");
333-
if (SPI_processed == 0)
356+
357+
/* Check for shared-cache-inval messages */
358+
AcceptInvalidationMessages();
359+
360+
/* Look up the appropriate relation using namespace search */
361+
relOid = RangeVarGetRelid(makeRangeVar(NULL, "pltcl_modules"), true);
362+
363+
/* Drop out on not-found */
364+
if (!OidIsValid(relOid))
334365
return;
335366

367+
/* Let relation_open do the rest */
368+
pmrel = relation_open(relOid, AccessShareLock);
369+
370+
if (pmrel == NULL)
371+
return;
372+
/* must be table or view, else ignore */
373+
if (!(pmrel->rd_rel->relkind == RELKIND_RELATION ||
374+
pmrel->rd_rel->relkind == RELKIND_VIEW))
375+
{
376+
relation_close(pmrel, AccessShareLock);
377+
return;
378+
}
379+
/* must be owned by superuser, else ignore */
380+
if (!superuser_arg(pmrel->rd_rel->relowner))
381+
{
382+
relation_close(pmrel, AccessShareLock);
383+
return;
384+
}
385+
/* get fully qualified table name for use in select command */
386+
nspname = get_namespace_name(RelationGetNamespace(pmrel));
387+
if (!nspname)
388+
elog(ERROR, "cache lookup failed for namespace %u",
389+
RelationGetNamespace(pmrel));
390+
pmrelname = quote_qualified_identifier(nspname,
391+
RelationGetRelationName(pmrel));
392+
336393
/************************************************************
337-
* Read all the row's from it where modname = 'unknown' in
338-
* the order of modseq
394+
* Read all the rows from it where modname = 'unknown',
395+
* in the order of modseq
339396
************************************************************/
340-
Tcl_DStringInit(&unknown_src);
397+
buflen = strlen(pmrelname) + 100;
398+
buf = (char *) palloc(buflen);
399+
snprintf(buf, buflen,
400+
"select modsrc from %s where modname = 'unknown' order by modseq",
401+
pmrelname);
341402

342-
spi_rc = SPI_exec("select modseq, modsrc from pltcl_modules "
343-
"where modname = 'unknown' "
344-
"order by modseq", 0);
403+
spi_rc = SPI_exec(buf, 0);
345404
if (spi_rc != SPI_OK_SELECT)
346405
elog(ERROR, "select from pltcl_modules failed");
347406

407+
pfree(buf);
408+
348409
/************************************************************
349410
* If there's nothing, module unknown doesn't exist
350411
************************************************************/
351412
if (SPI_processed == 0)
352413
{
353-
Tcl_DStringFree(&unknown_src);
354414
SPI_freetuptable(SPI_tuptable);
355415
elog(WARNING, "module \"unknown\" not found in pltcl_modules");
416+
relation_close(pmrel, AccessShareLock);
356417
return;
357418
}
358419

359420
/************************************************************
360-
* There is a module named unknown. Resemble the
421+
* There is a module named unknown. Reassemble the
361422
* source from the modsrc attributes and evaluate
362423
* it in the Tcl interpreter
363424
************************************************************/
364425
fno = SPI_fnumber(SPI_tuptable->tupdesc, "modsrc");
365426

427+
Tcl_DStringInit(&unknown_src);
428+
366429
for (i = 0; i < SPI_processed; i++)
367430
{
368431
part = SPI_getvalue(SPI_tuptable->vals[i],
@@ -376,8 +439,19 @@ pltcl_init_load_unknown(Tcl_Interp *interp)
376439
}
377440
}
378441
tcl_rc = Tcl_GlobalEval(interp, Tcl_DStringValue(&unknown_src));
442+
379443
Tcl_DStringFree(&unknown_src);
380444
SPI_freetuptable(SPI_tuptable);
445+
446+
if (tcl_rc != TCL_OK)
447+
{
448+
UTF_BEGIN;
449+
elog(ERROR, "could not load module \"unknown\": %s",
450+
UTF_U2E(Tcl_GetStringResult(interp)));
451+
UTF_END;
452+
}
453+
454+
relation_close(pmrel, AccessShareLock);
381455
}
382456

383457

@@ -398,9 +472,10 @@ pltcl_call_handler(PG_FUNCTION_ARGS)
398472
FunctionCallInfo save_fcinfo;
399473

400474
/************************************************************
401-
* Initialize interpreters
475+
* Initialize interpreters if not done previously
402476
************************************************************/
403-
pltcl_init_all();
477+
if (!pltcl_pm_init_done)
478+
pltcl_init();
404479

405480
/************************************************************
406481
* Connect to SPI manager
@@ -467,10 +542,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS)
467542
/* Find or compile the function */
468543
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid);
469544

470-
if (prodesc->lanpltrusted)
471-
interp = pltcl_safe_interp;
472-
else
473-
interp = pltcl_norm_interp;
545+
interp = pltcl_fetch_interp(prodesc->lanpltrusted);
474546

475547
/************************************************************
476548
* Create the tcl command to call the internal
@@ -654,10 +726,7 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS)
654726
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
655727
RelationGetRelid(trigdata->tg_relation));
656728

657-
if (prodesc->lanpltrusted)
658-
interp = pltcl_safe_interp;
659-
else
660-
interp = pltcl_norm_interp;
729+
interp = pltcl_fetch_interp(prodesc->lanpltrusted);
661730

662731
tupdesc = trigdata->tg_relation->rd_att;
663732

@@ -1078,10 +1147,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
10781147
prodesc->lanpltrusted = langStruct->lanpltrusted;
10791148
ReleaseSysCache(langTup);
10801149

1081-
if (prodesc->lanpltrusted)
1082-
interp = pltcl_safe_interp;
1083-
else
1084-
interp = pltcl_norm_interp;
1150+
interp = pltcl_fetch_interp(prodesc->lanpltrusted);
10851151

10861152
/************************************************************
10871153
* Get the required information for input conversion of the

0 commit comments

Comments
 (0)