Avoid memory leak in validation of a PL/Python trigger function.
authorTom Lane <[email protected]>
Thu, 23 Oct 2025 18:23:26 +0000 (14:23 -0400)
committerTom Lane <[email protected]>
Thu, 23 Oct 2025 18:23:26 +0000 (14:23 -0400)
If we're trying to perform check_function_bodies validation
of a PL/Python trigger function, we create a new PLyProcedure,
but we don't put it into the PLy_procedure_cache hash table.
(Doing so would be useless, since we don't have the relation
OID that is part of the cache key for a trigger function, so
we could not make an entry that would be found by later uses.)
However, we didn't think through what to do instead, with the
result that the PLyProcedure was simply leaked.

It would take a pretty large number of CREATE FUNCTION operations
for this to amount to a serious problem, but it's easy to see the
memory bloat if you do CREATE OR REPLACE FUNCTION in a loop.

To fix, have PLy_procedure_get delete the new PLyProcedure
and return NULL if it's not going to cache the PLyProcedure.
I considered making plpython3_validator do the cleanup instead,
which would be more natural.  But then plpython3_validator would
have to know the rules under which PLy_procedure_get returns a
non-cached PLyProcedure, else it risks deleting something that's
pointed to by a cache entry.  On the whole it seems more robust
to deal with the case inside PLy_procedure_get.

Found by the new version of Coverity (nice catch!).  In the end
I feel this fix is more about satisfying Coverity than about
fixing a real-world problem, so I'm not going to back-patch.

src/pl/plpython/plpy_main.c
src/pl/plpython/plpy_procedure.c

index 8ae02a239ae856af5a5c5590dac79f26d910fa1b..7673b5eca7b15ff7d796198e301319fd842d5fbd 100644 (file)
@@ -133,7 +133,7 @@ plpython3_validator(PG_FUNCTION_ARGS)
    ReleaseSysCache(tuple);
 
    /* We can't validate triggers against any particular table ... */
-   PLy_procedure_get(funcoid, InvalidOid, is_trigger);
+   (void) PLy_procedure_get(funcoid, InvalidOid, is_trigger);
 
    PG_RETURN_VOID();
 }
index 22d9ef0fe06773a4cb03e7ad5fe80069ef17e416..655ab1d09eea51eb630c7cd490c61a5a298944f3 100644 (file)
@@ -60,7 +60,12 @@ PLy_procedure_name(PLyProcedure *proc)
  *
  * The reason that both fn_rel and is_trigger need to be passed is that when
  * trigger functions get validated we don't know which relation(s) they'll
- * be used with, so no sensible fn_rel can be passed.
+ * be used with, so no sensible fn_rel can be passed.  Also, in that case
+ * we can't make a cache entry because we can't construct the right cache key.
+ * To forestall leakage of the PLyProcedure in such cases, delete it after
+ * construction and return NULL.  That's okay because the only caller that
+ * would pass that set of values is plpython3_validator, which ignores our
+ * result anyway.
  */
 PLyProcedure *
 PLy_procedure_get(Oid fn_oid, Oid fn_rel, PLyTrigType is_trigger)
@@ -102,6 +107,12 @@ PLy_procedure_get(Oid fn_oid, Oid fn_rel, PLyTrigType is_trigger)
            proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
            if (use_cache)
                entry->proc = proc;
+           else
+           {
+               /* Delete the proc, otherwise it's a memory leak */
+               PLy_procedure_delete(proc);
+               proc = NULL;
+           }
        }
        else if (!PLy_procedure_valid(proc, procTup))
        {