Make plperl safe against functions that are redefined while running.

validate_plperl_function() supposed that it could free an old
plperl_proc_desc struct immediately upon detecting that it was stale.
However, if a plperl function is called recursively, this could result
in deleting the struct out from under an outer invocation, leading to
misbehavior or crashes.  Add a simple reference-count mechanism to
ensure that such structs are freed only when the last reference goes
away.

Per investigation of bug #7516 from Marko Tiikkaja.  I am not certain
that this error explains his report, because he says he didn't have
any recursive calls --- but it's hard to see how else it could have
crashed right there.  In any case, this definitely fixes some problems
in the area.

Back-patch to all active branches.
This commit is contained in:
Tom Lane 2012-09-09 20:32:54 -04:00
parent 843363b8d5
commit 59f23fe8d4
3 changed files with 99 additions and 33 deletions

View File

@ -693,3 +693,21 @@ $$ LANGUAGE plperl;
SELECT text_scalarref(); SELECT text_scalarref();
ERROR: PL/Perl function must return reference to hash or array ERROR: PL/Perl function must return reference to hash or array
CONTEXT: PL/Perl function "text_scalarref" CONTEXT: PL/Perl function "text_scalarref"
-- check safe behavior when a function body is replaced during execution
CREATE OR REPLACE FUNCTION self_modify(INTEGER) RETURNS INTEGER AS $$
spi_exec_query('CREATE OR REPLACE FUNCTION self_modify(INTEGER) RETURNS INTEGER AS \'return $_[0] * 3;\' LANGUAGE plperl;');
spi_exec_query('select self_modify(42) AS a');
return $_[0] * 2;
$$ LANGUAGE plperl;
SELECT self_modify(42);
self_modify
-------------
84
(1 row)
SELECT self_modify(42);
self_modify
-------------
126
(1 row)

View File

@ -70,6 +70,7 @@ PG_MODULE_MAGIC;
* *
* The plperl_interp_desc structs are kept in a Postgres hash table indexed * 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. * by userid OID, with OID 0 used for the single untrusted interpreter.
* Once created, an interpreter is kept for the life of the process.
* *
* We start out by creating a "held" interpreter, which we initialize * 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 * only as far as we can do without deciding if it will be trusted or
@ -95,28 +96,44 @@ typedef struct plperl_interp_desc
/********************************************************************** /**********************************************************************
* The information we cache about loaded procedures * The information we cache about loaded procedures
*
* The refcount field counts the struct's reference from the hash table shown
* below, plus one reference for each function call level that is using the
* struct. We can release the struct, and the associated Perl sub, when the
* refcount goes to zero.
**********************************************************************/ **********************************************************************/
typedef struct plperl_proc_desc typedef struct plperl_proc_desc
{ {
char *proname; /* user name of procedure */ char *proname; /* user name of procedure */
TransactionId fn_xmin; TransactionId fn_xmin; /* xmin/TID of procedure's pg_proc tuple */
ItemPointerData fn_tid; ItemPointerData fn_tid;
int refcount; /* reference count of this struct */
SV *reference; /* CODE reference for Perl sub */
plperl_interp_desc *interp; /* interpreter it's created in */ plperl_interp_desc *interp; /* interpreter it's created in */
bool fn_readonly; bool fn_readonly; /* is function readonly (not volatile)? */
bool lanpltrusted; bool lanpltrusted; /* is it plperl, rather than plperlu? */
bool fn_retistuple; /* true, if function returns tuple */ bool fn_retistuple; /* true, if function returns tuple */
bool fn_retisset; /* true, if function returns set */ bool fn_retisset; /* true, if function returns set */
bool fn_retisarray; /* true if function returns array */ bool fn_retisarray; /* true if function returns array */
/* Conversion info for function's result type: */
Oid result_oid; /* Oid of result type */ Oid result_oid; /* Oid of result type */
FmgrInfo result_in_func; /* I/O function and arg for result type */ FmgrInfo result_in_func; /* I/O function and arg for result type */
Oid result_typioparam; Oid result_typioparam;
/* Conversion info for function's argument types: */
int nargs; int nargs;
FmgrInfo arg_out_func[FUNC_MAX_ARGS]; FmgrInfo arg_out_func[FUNC_MAX_ARGS];
bool arg_is_rowtype[FUNC_MAX_ARGS]; bool arg_is_rowtype[FUNC_MAX_ARGS];
Oid arg_arraytype[FUNC_MAX_ARGS]; /* InvalidOid if not an array */ Oid arg_arraytype[FUNC_MAX_ARGS]; /* InvalidOid if not an array */
SV *reference;
} plperl_proc_desc; } plperl_proc_desc;
#define increment_prodesc_refcount(prodesc) \
((prodesc)->refcount++)
#define decrement_prodesc_refcount(prodesc) \
do { \
if (--((prodesc)->refcount) <= 0) \
free_plperl_function(prodesc); \
} while(0)
/********************************************************************** /**********************************************************************
* For speedy lookup, we maintain a hash table mapping from * For speedy lookup, we maintain a hash table mapping from
* function OID + trigger flag + user OID to plperl_proc_desc pointers. * function OID + trigger flag + user OID to plperl_proc_desc pointers.
@ -238,6 +255,8 @@ static void set_interp_require(bool trusted);
static Datum plperl_func_handler(PG_FUNCTION_ARGS); static Datum plperl_func_handler(PG_FUNCTION_ARGS);
static Datum plperl_trigger_handler(PG_FUNCTION_ARGS); static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
static void free_plperl_function(plperl_proc_desc *prodesc);
static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger); static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger);
static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc); static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc);
@ -1689,6 +1708,7 @@ plperl_call_handler(PG_FUNCTION_ARGS)
PG_TRY(); PG_TRY();
{ {
current_call_data = NULL;
if (CALLED_AS_TRIGGER(fcinfo)) if (CALLED_AS_TRIGGER(fcinfo))
retval = PointerGetDatum(plperl_trigger_handler(fcinfo)); retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
else else
@ -1696,12 +1716,16 @@ plperl_call_handler(PG_FUNCTION_ARGS)
} }
PG_CATCH(); PG_CATCH();
{ {
if (current_call_data && current_call_data->prodesc)
decrement_prodesc_refcount(current_call_data->prodesc);
current_call_data = save_call_data; current_call_data = save_call_data;
activate_interpreter(oldinterp); activate_interpreter(oldinterp);
PG_RE_THROW(); PG_RE_THROW();
} }
PG_END_TRY(); PG_END_TRY();
if (current_call_data && current_call_data->prodesc)
decrement_prodesc_refcount(current_call_data->prodesc);
current_call_data = save_call_data; current_call_data = save_call_data;
activate_interpreter(oldinterp); activate_interpreter(oldinterp);
return retval; return retval;
@ -1753,14 +1777,15 @@ plperl_inline_handler(PG_FUNCTION_ARGS)
desc.nargs = 0; desc.nargs = 0;
desc.reference = NULL; desc.reference = NULL;
current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data));
current_call_data->fcinfo = &fake_fcinfo;
current_call_data->prodesc = &desc;
PG_TRY(); PG_TRY();
{ {
SV *perlret; SV *perlret;
current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data));
current_call_data->fcinfo = &fake_fcinfo;
current_call_data->prodesc = &desc;
/* we do not bother with refcounting the fake prodesc */
if (SPI_connect() != SPI_OK_CONNECT) if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager"); elog(ERROR, "could not connect to SPI manager");
@ -2154,6 +2179,7 @@ plperl_func_handler(PG_FUNCTION_ARGS)
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false); prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false);
current_call_data->prodesc = prodesc; current_call_data->prodesc = prodesc;
increment_prodesc_refcount(prodesc);
/* Set a callback for error reporting */ /* Set a callback for error reporting */
pl_error_context.callback = plperl_exec_callback; pl_error_context.callback = plperl_exec_callback;
@ -2274,6 +2300,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */ /* Find or compile the function */
prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true); prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true);
current_call_data->prodesc = prodesc; current_call_data->prodesc = prodesc;
increment_prodesc_refcount(prodesc);
/* Set a callback for error reporting */ /* Set a callback for error reporting */
pl_error_context.callback = plperl_exec_callback; pl_error_context.callback = plperl_exec_callback;
@ -2383,7 +2410,19 @@ validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup)
/* Otherwise, unlink the obsoleted entry from the hashtable ... */ /* Otherwise, unlink the obsoleted entry from the hashtable ... */
proc_ptr->proc_ptr = NULL; proc_ptr->proc_ptr = NULL;
/* ... and throw it away */ /* ... and release the corresponding refcount, probably deleting it */
decrement_prodesc_refcount(prodesc);
}
return false;
}
static void
free_plperl_function(plperl_proc_desc *prodesc)
{
Assert(prodesc->refcount <= 0);
/* Release CODE reference, if we have one, from the appropriate interp */
if (prodesc->reference) if (prodesc->reference)
{ {
plperl_interp_desc *oldinterp = plperl_active_interp; plperl_interp_desc *oldinterp = plperl_active_interp;
@ -2392,11 +2431,11 @@ validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup)
SvREFCNT_dec(prodesc->reference); SvREFCNT_dec(prodesc->reference);
activate_interpreter(oldinterp); activate_interpreter(oldinterp);
} }
/* Get rid of what we conveniently can of our own structs */
/* (FmgrInfo subsidiary info will get leaked ...) */
if (prodesc->proname)
free(prodesc->proname); free(prodesc->proname);
free(prodesc); free(prodesc);
}
return false;
} }
@ -2470,12 +2509,17 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY), (errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory"))); errmsg("out of memory")));
/* Initialize all fields to 0 so free_plperl_function is safe */
MemSet(prodesc, 0, sizeof(plperl_proc_desc)); MemSet(prodesc, 0, sizeof(plperl_proc_desc));
prodesc->proname = strdup(NameStr(procStruct->proname)); prodesc->proname = strdup(NameStr(procStruct->proname));
if (prodesc->proname == NULL) if (prodesc->proname == NULL)
{
free_plperl_function(prodesc);
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY), (errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory"))); errmsg("out of memory")));
}
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
prodesc->fn_tid = procTup->t_self; prodesc->fn_tid = procTup->t_self;
@ -2490,8 +2534,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
ObjectIdGetDatum(procStruct->prolang)); ObjectIdGetDatum(procStruct->prolang));
if (!HeapTupleIsValid(langTup)) if (!HeapTupleIsValid(langTup))
{ {
free(prodesc->proname); free_plperl_function(prodesc);
free(prodesc);
elog(ERROR, "cache lookup failed for language %u", elog(ERROR, "cache lookup failed for language %u",
procStruct->prolang); procStruct->prolang);
} }
@ -2510,8 +2553,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
ObjectIdGetDatum(procStruct->prorettype)); ObjectIdGetDatum(procStruct->prorettype));
if (!HeapTupleIsValid(typeTup)) if (!HeapTupleIsValid(typeTup))
{ {
free(prodesc->proname); free_plperl_function(prodesc);
free(prodesc);
elog(ERROR, "cache lookup failed for type %u", elog(ERROR, "cache lookup failed for type %u",
procStruct->prorettype); procStruct->prorettype);
} }
@ -2525,8 +2567,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
/* okay */ ; /* okay */ ;
else if (procStruct->prorettype == TRIGGEROID) else if (procStruct->prorettype == TRIGGEROID)
{ {
free(prodesc->proname); free_plperl_function(prodesc);
free(prodesc);
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called " errmsg("trigger functions can only be called "
@ -2534,8 +2575,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
} }
else else
{ {
free(prodesc->proname); free_plperl_function(prodesc);
free(prodesc);
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Perl functions cannot return type %s", errmsg("PL/Perl functions cannot return type %s",
@ -2570,8 +2610,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
ObjectIdGetDatum(procStruct->proargtypes.values[i])); ObjectIdGetDatum(procStruct->proargtypes.values[i]));
if (!HeapTupleIsValid(typeTup)) if (!HeapTupleIsValid(typeTup))
{ {
free(prodesc->proname); free_plperl_function(prodesc);
free(prodesc);
elog(ERROR, "cache lookup failed for type %u", elog(ERROR, "cache lookup failed for type %u",
procStruct->proargtypes.values[i]); procStruct->proargtypes.values[i]);
} }
@ -2581,8 +2620,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
if (typeStruct->typtype == TYPTYPE_PSEUDO && if (typeStruct->typtype == TYPTYPE_PSEUDO &&
procStruct->proargtypes.values[i] != RECORDOID) procStruct->proargtypes.values[i] != RECORDOID)
{ {
free(prodesc->proname); free_plperl_function(prodesc);
free(prodesc);
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PL/Perl functions cannot accept type %s", errmsg("PL/Perl functions cannot accept type %s",
@ -2635,8 +2673,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
pfree(proc_source); pfree(proc_source);
if (!prodesc->reference) /* can this happen? */ if (!prodesc->reference) /* can this happen? */
{ {
free(prodesc->proname); free_plperl_function(prodesc);
free(prodesc);
elog(ERROR, "could not create PL/Perl internal procedure"); elog(ERROR, "could not create PL/Perl internal procedure");
} }
@ -2648,6 +2685,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
proc_ptr = hash_search(plperl_proc_hash, &proc_key, proc_ptr = hash_search(plperl_proc_hash, &proc_key,
HASH_ENTER, NULL); HASH_ENTER, NULL);
proc_ptr->proc_ptr = prodesc; proc_ptr->proc_ptr = prodesc;
increment_prodesc_refcount(prodesc);
} }
/* restore previous error callback */ /* restore previous error callback */

View File

@ -462,3 +462,13 @@ CREATE OR REPLACE FUNCTION text_scalarref() RETURNS text AS $$
$$ LANGUAGE plperl; $$ LANGUAGE plperl;
SELECT text_scalarref(); SELECT text_scalarref();
-- check safe behavior when a function body is replaced during execution
CREATE OR REPLACE FUNCTION self_modify(INTEGER) RETURNS INTEGER AS $$
spi_exec_query('CREATE OR REPLACE FUNCTION self_modify(INTEGER) RETURNS INTEGER AS \'return $_[0] * 3;\' LANGUAGE plperl;');
spi_exec_query('select self_modify(42) AS a');
return $_[0] * 2;
$$ LANGUAGE plperl;
SELECT self_modify(42);
SELECT self_modify(42);