Make plperl safe against functions that are redefined while running.
authorTom Lane <[email protected]>
Mon, 10 Sep 2012 00:33:14 +0000 (20:33 -0400)
committerTom Lane <[email protected]>
Mon, 10 Sep 2012 00:33:14 +0000 (20:33 -0400)
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.

src/pl/plperl/expected/plperl.out
src/pl/plperl/plperl.c
src/pl/plperl/sql/plperl.sql

index 6baa1275046073e7445804f35839fa14f3e6aa82..3643885e1926bcf533eddb98cb7d60c1b714093e 100644 (file)
@@ -503,3 +503,21 @@ CREATE OR REPLACE FUNCTION perl_unsafe1() RETURNS void AS $$
    my $fd = fileno STDERR;
 $$ LANGUAGE plperl;
 ERROR:  creation of Perl function "perl_unsafe1" failed: 'fileno' trapped by operation mask at line 2.
+-- 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)
+
index 0d7e3b94e7fe5ac8018e74e5d9b0c2f5d3d5ab21..b5cadc31d7e63860cc2e3f0fe97e2d05c09e59be 100644 (file)
@@ -52,6 +52,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
@@ -77,27 +78,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.
@@ -195,6 +212,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);
@@ -1007,6 +1026,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
@@ -1014,12 +1034,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;
@@ -1368,6 +1392,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);
 
    rsi = (ReturnSetInfo *) fcinfo->resultinfo;
 
@@ -1521,6 +1546,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);
 
    activate_interpreter(prodesc->interp);
 
@@ -1618,23 +1644,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)
 {
@@ -1700,12 +1738,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;
 
@@ -1721,8 +1764,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                                 0, 0, 0);
        if (!HeapTupleIsValid(langTup))
        {
-           free(prodesc->proname);
-           free(prodesc);
+           free_plperl_function(prodesc);
            elog(ERROR, "cache lookup failed for language %u",
                 procStruct->prolang);
        }
@@ -1741,8 +1783,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                                     0, 0, 0);
            if (!HeapTupleIsValid(typeTup))
            {
-               free(prodesc->proname);
-               free(prodesc);
+               free_plperl_function(prodesc);
                elog(ERROR, "cache lookup failed for type %u",
                     procStruct->prorettype);
            }
@@ -1756,8 +1797,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 "
@@ -1765,8 +1805,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("plperl functions cannot return type %s",
@@ -1802,8 +1841,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
                                         0, 0, 0);
                if (!HeapTupleIsValid(typeTup))
                {
-                   free(prodesc->proname);
-                   free(prodesc);
+                   free_plperl_function(prodesc);
                    elog(ERROR, "cache lookup failed for type %u",
                         procStruct->proargtypes.values[i]);
                }
@@ -1812,8 +1850,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("plperl functions cannot take type %s",
@@ -1860,8 +1897,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");
        }
 
@@ -1873,6 +1909,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);
    }
 
    ReleaseSysCache(procTup);
index 53b539ed2e7d5963e4e3b539415000468100fc3b..60c1795bb65c9ca384341fb1a261f77f0c0f6e25 100644 (file)
@@ -366,3 +366,13 @@ SELECT perl_spi_prepared_bad(4.35) as "double precision";
 CREATE OR REPLACE FUNCTION perl_unsafe1() RETURNS void AS $$
    my $fd = fileno STDERR;
 $$ 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);