[PATCH] Limit PL/Perl scalar copies to work_mem
Andrey Rachitskiy <pl0h0yp1@gmail.com>
From: Andrey Rachitskiy <pl0h0yp1@gmail.com>
To:
Date: 2026-07-06T22:13:53Z
Lists: pgsql-hackers
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--