| From: | Andrey Rachitskiy <pl0h0yp1(at)gmail(dot)com> |
|---|---|
| To: | 1217816127(at)qq(dot)com, pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Subject: | Re: BUG #19601: Vuln45: Unbounded recursion via self-retying Perl scalar in bool_plperl's SvTRUE call causes backend |
| Date: | 2026-08-03 21:10:50 |
| Message-ID: | CAB8bMiuZMHtW+2E-brFjy5wPttdkf5JF=FoQuDj5xR_9tBcdog@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
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(at)postgresql(dot)org>:
> The following bug has been logged on the website:
>
> Bug reference: 19601
> Logged by: Yuelin Wang
> Email address: 1217816127(at)qq(dot)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.
>
>
>
>
>
--
Regards,
Rachitskiy Andrey
| Attachment | Content-Type | Size |
|---|---|---|
| 0001-BUG-19601-Fix-recursively-tied-Perl-return-values.patch | text/x-patch | 13.9 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Tom Lane | 2026-08-03 22:01:50 | Re: BUG #19601: Vuln45: Unbounded recursion via self-retying Perl scalar in bool_plperl's SvTRUE call causes backend |
| Previous Message | Alexander Korotkov | 2026-08-03 19:26:00 | Re: MERGE/SPLIT PARTITIONS issues/questions |