Thread
-
[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--