Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wgs0w-006Sxv-07 for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Jul 2026 22:42:10 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wgs0u-003U4T-1v for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Jul 2026 22:42:08 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wgs0u-003U4L-0u for pgsql-hackers@lists.postgresql.org; Mon, 06 Jul 2026 22:42:08 +0000 Received: from mail-lf1-x134.google.com ([2a00:1450:4864:20::134]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wgs0s-000000028Sa-1NSq for pgsql-hackers@lists.postgresql.org; Mon, 06 Jul 2026 22:42:08 +0000 Received: by mail-lf1-x134.google.com with SMTP id 2adb3069b0e04-5aeae350e0aso3306932e87.1 for ; Mon, 06 Jul 2026 15:42:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783377725; x=1783982525; darn=lists.postgresql.org; h=mime-version:message-id:subject:cc:from:date:from:to:cc:subject :date:message-id:reply-to; bh=hQaCZfNZ/07uRKkmn1Hy4FLVJVgM6NQKdppW7OJNqq4=; b=GtyG1l3jXOIGILmKxG4Dkg4n29K4pWS9aWSi00sI6cB0il4h6+jbSAmQEP7nWiiL8T m5r7AKeYpRGc5Yu1oL5ZJnVwVDW52IKZKhLOgv3VTMhg2jqdOwu8g3iB33r8gTWy4HRo oagU3sKncNWq+DYVuZ/a2KpGjxz19TlSk4bod4iJquE2OAJPb4watFyAeVwEU/A3CtV7 p50v4uAE6MSYMP3CywQoMMColRB7WELbHk6PB3CmfpPR9Q/lRGsFnmMZd9K+tsjUnjpI kRdqLLYvpIGL5dZSSY5ld0J9k2yYIIYmSkRY7KtXjEwTuy+WRx4Wq89L2efRnE1uNXty ZPCQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783377725; x=1783982525; h=mime-version:message-id:subject:cc:from:date:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=hQaCZfNZ/07uRKkmn1Hy4FLVJVgM6NQKdppW7OJNqq4=; b=q5MJk3u6aPQn4EQEcOlZ5rp8g3AiknTzoyUdLSpGHFlQTlziSD9NsOzucWe0mOZQOD +qbNNjYtnuHj0mC2Bext0Uw9iT5/kfnjK2eIWWVcdmWMTUgD5uoxEiG+h5nZbWWcQ7hv XIJN7txgjmSsS6QWenK2wEnkwr5QwSDe8+wkL7w3KiDtR0/SMxeQIdhsP4ezqyzk0+BE qoi9L0E6Rg8o6bDnCQylXorx/CtlK7N5PmyUKMEFDksY/VZxijkj95T7iqYj+9kRQkla g0SgKJH3fftwLbUe/a1LoyiH7g3c8/kV4GG9cjtywJRBASX3SAqJHzwPKAISz+Qw31Sr Ejqg== X-Gm-Message-State: AOJu0YzP08ElYTQm+U79MfY6pugyptGWGwaex6cg3SiFt5eEtWyoh+Sn ua6udnWVKwHay+cePk/wsntGFNhNfF4dvzA4v1SxVTK8RWkquvGFhJEVslhlWMhaSzWTdg== X-Gm-Gg: AfdE7cm3nOhCKoy0JNDChU9jxHlSIFgQ7jsVXXCJj1tmKjAjzApHGjQsJ3bF0opTz+l IO3dlDyOR2OumlWsE8Rv/0fEoAJyCEx7fADOmZtFhiKde6me1yOkLpaOAdiuzRlhPJJErzFpP5x mWpErE53KiXu+8d3KM2n1bdZcDM4fmJA874WdPWVZWuWz4Ws6syjQsp6aWcDnfFYwDZ37djf5OK rLZ5hrJahGz/aGhiwXFUqtGTLavZG1CAgWMmqZnriwuAnm6a5PPznXNLPGn4dIVRl8fZTGKyzun fl91DvOFUnl/Ix8Siwl4dVusSnAuc1ndcjq/GbSYFLBMxCBm5Cet9FwXmwC6Y5IRYxeVAeOsCCV R+Yx/OdVmFw1prqRq8mldF4SZ+8j5sIzm5yFcwztqVTl6BJEWa/I+kEZO/eWwiEn8lSsaHvMyc6 viBI2bJ6GFHmFSjQiEl0OnnLg= X-Received: by 2002:a05:6512:1107:b0:5ae:b59e:aa45 with SMTP id 2adb3069b0e04-5b007b7926cmr516248e87.17.1783377725057; Mon, 06 Jul 2026 15:42:05 -0700 (PDT) Received: from pg-ThinkPad-T14-Gen-4 ([93.174.132.69]) by smtp.gmail.com with ESMTPSA id 2adb3069b0e04-5aed1370105sm3163917e87.7.2026.07.06.15.42.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Jul 2026 15:42:03 -0700 (PDT) Date: Tue, 7 Jul 2026 03:41:57 +0500 From: Andrey Rachitskiy Cc: PostgreSQL Hackers , Nikolay Shaplov Subject: [PATCH] Limit PL/Perl scalar copies to work_mem Message-ID: <20260707034157.5e88bf34@pg-ThinkPad-T14-Gen-4> X-Mailer: Claws Mail 4.3.1 (GTK 3.24.52; x86_64-pc-linux-gnu) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="MP_/sDTAhto+gkG/PMs8Alw6m8e" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --MP_/sDTAhto+gkG/PMs8Alw6m8e Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Content-Disposition: inline Hi, Hackers! When a PL/Perl function returns a large text value, sv2cstr() copies the entire Perl string into backend memory with no size check. The helper is used on the path from Perl return values and SPI arguments to PostgreSQL text datums; it simply palloc()s a copy after SvPVutf8(). A user who is allowed to create untrusted PL/Perl functions can therefore force the backend to allocate strings far larger than any session limit. On a memory-constrained host this can get the backend process killed by the OOM killer (SIGKILL) rather than raising a catchable PostgreSQL error. Reproducer (unpatched master, plperl enabled): CREATE FUNCTION perl_huge_text() RETURNS text LANGUAGE plperl AS $$ return 'x' x (1024 * 1024 * 1024); $$; SELECT perl_huge_text(); On a container limited to about 768MB RAM, CREATE FUNCTION alone is enough to lose the backend: LOG: client backend (PID ...) was terminated by signal 9: Killed DETAIL: Failed process was running: CREATE OR REPLACE FUNCTION ... With plenty of free RAM the same code may succeed instead, which I think shows missing enforcement rather than an intentional "no limit" design: other PL/Perl paths already enforce bounds (MAXDIM, AV_SIZE_MAX for SPI results, max_stack_depth in recursive conversion), but sv2cstr() had none. This patch rejects Perl strings larger than work_mem * 1024 bytes, capped by MaxAllocSize, before copying them through sv2cstr(). That follows the same work_mem-based pattern used elsewhere in the backend for per-query working storage. The check is done after SvPVutf8() has reported the length but before utf_u2e() allocates the database-encoding copy. A plperl regression test returns a 16MB string with the default 4MB work_mem and expects: ERROR: Perl value exceeds maximum allowed size (4194304 bytes) HINT: Increase work_mem or reduce the result size. Legitimate functions that need to move more data can raise work_mem for the session, consistent with other operations bounded by that GUC. Comments welcome. -- Regards, Andrey Rachitskiy --MP_/sDTAhto+gkG/PMs8Alw6m8e Content-Type: text/x-patch Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=0001-plperl-limit-scalar-size.patch From 50f5b04fd79548e65081db6a52478ee7faeb629b Mon Sep 17 00:00:00 2001 From: Andrey Rachitskiy Date: Mon, 06 Jul 2026 22:13:53 +0000 Subject: [PATCH] Limit PL/Perl scalar copies to work_mem 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 --- 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--