Thread

  1. [PATCH] Limit PL/Perl scalar copies to work_mem

    Andrey Rachitskiy <pl0h0yp1@gmail.com> — 2026-07-06T22:13:53Z

    When a PL/Perl function returns a very large text value, sv2cstr() copies
    the entire Perl string into backend memory with no size check.  A user
    with permission to create untrusted PL/Perl functions can return strings
    far larger than work_mem and risk getting the backend killed by the OOM
    killer.
    
    Reject Perl strings larger than work_mem * 1024 bytes, capped by
    MaxAllocSize, before copying them through sv2cstr().  This follows the
    same work_mem-based limit pattern used elsewhere in the backend.
    
    Add a plperl regression test that attempts to return a 16MB string with
    the default 4MB work_mem setting.
    
    Author: Andrey Rachitskiy <pl0h0yp1@gmail.com>
    ---
     src/pl/plperl/expected/plperl.out |  8 +++++++
     src/pl/plperl/plperl.h            | 34 ++++++++++++++++++++++++++++++
     src/pl/plperl/sql/plperl.sql      |  7 ++++++
     3 files changed, 49 insertions(+)
    diff --git a/src/pl/plperl/expected/plperl.out b/src/pl/plperl/expected/plperl.out
    index e3d7c88..50c788b 100644
    --- a/src/pl/plperl/expected/plperl.out
    +++ b/src/pl/plperl/expected/plperl.out
    @@ -792,3 +792,11 @@ SELECT self_modify(42);
              126
     (1 row)
     
    +-- oversized text results are rejected at the PL boundary
    +CREATE OR REPLACE FUNCTION perl_oversized_text() RETURNS text AS $$
    +	return 'x' x (16 * 1024 * 1024);
    +$$ LANGUAGE plperl;
    +SELECT perl_oversized_text();
    +ERROR:  Perl value exceeds maximum allowed size (4194304 bytes)
    +HINT:  Increase work_mem or reduce the result size.
    +CONTEXT:  PL/Perl function "perl_oversized_text"
    diff --git a/src/pl/plperl/plperl.h b/src/pl/plperl/plperl.h
    index 4c03f9e..1ccce6d 100644
    --- a/src/pl/plperl/plperl.h
    +++ b/src/pl/plperl/plperl.h
    @@ -17,6 +17,8 @@
     
     /* defines free() by way of system headers, so must be included before perl.h */
     #include "mb/pg_wchar.h"
    +#include "miscadmin.h"
    +#include "utils/memutils.h"
     
     /*
      * Pull in Perl headers via a wrapper header, to control the scope of
    @@ -40,6 +42,36 @@ char	   *plperl_sv_to_literal(SV *, char *);
     void		plperl_util_elog(int level, SV *msg);
     
     
    +/*
    + * Maximum byte size for a Perl scalar copied through sv2cstr().
    + *
    + * This follows the same work_mem * 1024 pattern used elsewhere in the
    + * backend (e.g. reorderbuffer.c, nodeHash.c) and is capped by MaxAllocSize.
    + */
    +static inline Size
    +plperl_max_scalar_bytes(void)
    +{
    +	Size		limit = (Size) work_mem * (Size) 1024;
    +
    +	return Min(limit, MaxAllocSize - 1);
    +}
    +
    +/*
    + * Reject Perl strings that are too large to copy into backend memory.
    + */
    +static inline void
    +plperl_check_sv_length(STRLEN len)
    +{
    +	Size		max_len = plperl_max_scalar_bytes();
    +
    +	if ((Size) len > max_len)
    +		erereport(ERROR,
    +				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
    +				 errmsg("Perl value exceeds maximum allowed size (%zu bytes)",
    +						max_len),
    +			 errhint("Increase work_mem or reduce the result size.")));
    +}
    +
     /* helper functions */
     
     /*
    @@ -127,6 +159,8 @@ sv2cstr(SV *sv)
     	else
     		val = SvPVutf8(sv, len);
     
    +	plperl_check_sv_length(len);
    +
     	/*
     	 * Now convert to database encoding.  We use perl's length in the event we
     	 * had an embedded null byte to ensure we error out properly.
    diff --git a/src/pl/plperl/sql/plperl.sql b/src/pl/plperl/sql/plperl.sql
    index bb0b8ce..0470a2b 100644
    --- a/src/pl/plperl/sql/plperl.sql
    +++ b/src/pl/plperl/sql/plperl.sql
    @@ -521,3 +521,10 @@ $$ LANGUAGE plperl;
     
     SELECT self_modify(42);
     SELECT self_modify(42);
    +
    +-- oversized text results are rejected at the PL boundary
    +CREATE OR REPLACE FUNCTION perl_oversized_text() RETURNS text AS $$
    +	return 'x' x (16 * 1024 * 1024);
    +$$ LANGUAGE plperl;
    +
    +SELECT perl_oversized_text();
    
    --MP_/sDTAhto+gkG/PMs8Alw6m8e--