diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2012-09-09 20:33:06 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2012-09-09 20:33:06 -0400 |
commit | 0952811c86ad64d016d91ced0d8f2a78ce3acda2 (patch) | |
tree | 9796c900e7e6ede8f3c9c977d51b80715eec6987 | |
parent | 25b6df1e35190270b2cd04fd3beec7fed8441829 (diff) | |
download | postgresql-0952811c86ad64d016d91ced0d8f2a78ce3acda2.tar.gz postgresql-0952811c86ad64d016d91ced0d8f2a78ce3acda2.zip |
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.
-rw-r--r-- | src/pl/plperl/expected/plperl.out | 18 | ||||
-rw-r--r-- | src/pl/plperl/plperl.c | 104 | ||||
-rw-r--r-- | src/pl/plperl/sql/plperl.sql | 9 |
3 files changed, 98 insertions, 33 deletions
diff --git a/src/pl/plperl/expected/plperl.out b/src/pl/plperl/expected/plperl.out index e3e9ec7b6f8..24b537be11b 100644 --- a/src/pl/plperl/expected/plperl.out +++ b/src/pl/plperl/expected/plperl.out @@ -597,3 +597,21 @@ CONTEXT: PL/Perl anonymous code block DO $do$ use warnings FATAL => qw(void) ; my @y; my $x = sort @y; 1; $do$ LANGUAGE plperl; ERROR: Useless use of sort in scalar context at line 1. CONTEXT: PL/Perl anonymous code block +-- 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) + diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index cad1597fb2b..7c34f14b566 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -67,6 +67,7 @@ PG_MODULE_MAGIC; * * 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. + * Once created, an interpreter is kept for the life of the process. * * 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 @@ -92,27 +93,43 @@ typedef struct plperl_interp_desc /********************************************************************** * 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 { char *proname; /* user name of procedure */ - TransactionId fn_xmin; + TransactionId fn_xmin; /* xmin/TID of procedure's pg_proc tuple */ 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 */ - bool fn_readonly; - bool lanpltrusted; + bool fn_readonly; /* is function readonly (not volatile)? */ + bool lanpltrusted; /* is it plperl, rather than plperlu? */ bool fn_retistuple; /* true, if function returns tuple */ bool fn_retisset; /* true, if function returns set */ bool fn_retisarray; /* true if function returns array */ + /* Conversion info for function's result type: */ Oid result_oid; /* Oid of result type */ FmgrInfo result_in_func; /* I/O function and arg for result type */ Oid result_typioparam; + /* Conversion info for function's argument types: */ int nargs; FmgrInfo arg_out_func[FUNC_MAX_ARGS]; bool arg_is_rowtype[FUNC_MAX_ARGS]; - SV *reference; } 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 * function OID + trigger flag + user OID to plperl_proc_desc pointers. @@ -217,6 +234,8 @@ static void set_interp_require(bool trusted); static Datum plperl_func_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 SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc); @@ -1228,6 +1247,7 @@ plperl_call_handler(PG_FUNCTION_ARGS) PG_TRY(); { + current_call_data = NULL; if (CALLED_AS_TRIGGER(fcinfo)) retval = PointerGetDatum(plperl_trigger_handler(fcinfo)); else @@ -1235,12 +1255,16 @@ plperl_call_handler(PG_FUNCTION_ARGS) } PG_CATCH(); { + if (current_call_data && current_call_data->prodesc) + decrement_prodesc_refcount(current_call_data->prodesc); current_call_data = save_call_data; activate_interpreter(oldinterp); PG_RE_THROW(); } PG_END_TRY(); + if (current_call_data && current_call_data->prodesc) + decrement_prodesc_refcount(current_call_data->prodesc); current_call_data = save_call_data; activate_interpreter(oldinterp); return retval; @@ -1292,14 +1316,15 @@ plperl_inline_handler(PG_FUNCTION_ARGS) desc.nargs = 0; 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(); { 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) elog(ERROR, "could not connect to SPI manager"); @@ -1659,6 +1684,7 @@ plperl_func_handler(PG_FUNCTION_ARGS) prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false); current_call_data->prodesc = prodesc; + increment_prodesc_refcount(prodesc); /* Set a callback for error reporting */ pl_error_context.callback = plperl_exec_callback; @@ -1820,6 +1846,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) /* Find or compile the function */ prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true); current_call_data->prodesc = prodesc; + increment_prodesc_refcount(prodesc); /* Set a callback for error reporting */ pl_error_context.callback = plperl_exec_callback; @@ -1928,23 +1955,35 @@ validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup) /* Otherwise, unlink the obsoleted entry from the hashtable ... */ proc_ptr->proc_ptr = NULL; - /* ... and throw it away */ - if (prodesc->reference) - { - plperl_interp_desc *oldinterp = plperl_active_interp; - - activate_interpreter(prodesc->interp); - SvREFCNT_dec(prodesc->reference); - activate_interpreter(oldinterp); - } - free(prodesc->proname); - free(prodesc); + /* ... 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) + { + plperl_interp_desc *oldinterp = plperl_active_interp; + + activate_interpreter(prodesc->interp); + SvREFCNT_dec(prodesc->reference); + 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); +} + + static plperl_proc_desc * compile_plperl_function(Oid fn_oid, bool is_trigger) { @@ -2015,12 +2054,17 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) ereport(ERROR, (errcode(ERRCODE_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)); + prodesc->proname = strdup(NameStr(procStruct->proname)); if (prodesc->proname == NULL) + { + free_plperl_function(prodesc); ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); + } prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); prodesc->fn_tid = procTup->t_self; @@ -2035,8 +2079,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) ObjectIdGetDatum(procStruct->prolang)); if (!HeapTupleIsValid(langTup)) { - free(prodesc->proname); - free(prodesc); + free_plperl_function(prodesc); elog(ERROR, "cache lookup failed for language %u", procStruct->prolang); } @@ -2055,8 +2098,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) ObjectIdGetDatum(procStruct->prorettype)); if (!HeapTupleIsValid(typeTup)) { - free(prodesc->proname); - free(prodesc); + free_plperl_function(prodesc); elog(ERROR, "cache lookup failed for type %u", procStruct->prorettype); } @@ -2070,8 +2112,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) /* okay */ ; else if (procStruct->prorettype == TRIGGEROID) { - free(prodesc->proname); - free(prodesc); + free_plperl_function(prodesc); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called " @@ -2079,8 +2120,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) } else { - free(prodesc->proname); - free(prodesc); + free_plperl_function(prodesc); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Perl functions cannot return type %s", @@ -2115,8 +2155,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) ObjectIdGetDatum(procStruct->proargtypes.values[i])); if (!HeapTupleIsValid(typeTup)) { - free(prodesc->proname); - free(prodesc); + free_plperl_function(prodesc); elog(ERROR, "cache lookup failed for type %u", procStruct->proargtypes.values[i]); } @@ -2125,8 +2164,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) /* Disallow pseudotype argument */ if (typeStruct->typtype == TYPTYPE_PSEUDO) { - free(prodesc->proname); - free(prodesc); + free_plperl_function(prodesc); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("PL/Perl functions cannot accept type %s", @@ -2172,8 +2210,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) pfree(proc_source); if (!prodesc->reference) /* can this happen? */ { - free(prodesc->proname); - free(prodesc); + free_plperl_function(prodesc); elog(ERROR, "could not create PL/Perl internal procedure"); } @@ -2185,6 +2222,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) proc_ptr = hash_search(plperl_proc_hash, &proc_key, HASH_ENTER, NULL); proc_ptr->proc_ptr = prodesc; + increment_prodesc_refcount(prodesc); } /* restore previous error callback */ diff --git a/src/pl/plperl/sql/plperl.sql b/src/pl/plperl/sql/plperl.sql index 651d5ee2b41..7f02b2fe225 100644 --- a/src/pl/plperl/sql/plperl.sql +++ b/src/pl/plperl/sql/plperl.sql @@ -391,3 +391,12 @@ DO $do$ use strict; my $name = "foo"; my $ref = $$name; $do$ LANGUAGE plperl; -- yields "ERROR: Useless use of sort in scalar context." DO $do$ use warnings FATAL => qw(void) ; my @y; my $x = sort @y; 1; $do$ LANGUAGE plperl; +-- 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); |