[PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Started by Aleksander Alekseev7 days ago11 messageshackers
Jump to latest
#1Aleksander Alekseev
aleksander@timescale.com

Hi,

I discovered several bugs in jsonb_plperl and jsonb_plpython.

The first bug causes a segfault when dealing with deeply nested JSONB
values. As an example:

```
$ ./reproduce_stack_overflow.py
plpython3u (depth=100000): SIGSEGV
2026-06-16 16:42:56.989 MSK [3209763] LOG: client backend (PID
3209810) was terminated by signal 11: Segmentation fault
2026-06-16 16:42:56.989 MSK [3209763] DETAIL: Failed process was
running: SELECT py_deep(100000);
plperl (depth=100000): SIGSEGV
2026-06-16 16:42:59.101 MSK [3209763] LOG: client backend (PID
3209827) was terminated by signal 11: Segmentation fault
2026-06-16 16:42:59.101 MSK [3209763] DETAIL: Failed process was
running: SELECT perl_deep(100000);
```

The second bug affects only jsonb_plperl. It's possible to construct a
Perl object with circular references which will cause
SV_to_JsonbValue() to go into an infinite loop here:

```
while (SvROK(in))
in = SvRV(in);
```

The attached script reproduce_circular_ref.py reproduces the issue. Be
careful if you decide to run it because the backend will become
unresponsive to pg_cancel_backend() and you will be unable to stop the
cluster in a standard way.

I suggest fixing it by rewriting the while loop into a recursion with
check_stack_depth() call. This will make the behavior consistent with
jsonb_plpython.

Patches are attached. Thoughts?

--
Best regards,
Aleksander Alekseev

Attachments:

v1-0002-jsonb_plperl-Replace-reference-unwinding-loop-wit.patchtext/x-patch; charset=US-ASCII; name=v1-0002-jsonb_plperl-Replace-reference-unwinding-loop-wit.patchDownload+15-4
v1-0001-jsonb_plperl-jsonb_plpython-Add-missing-check_sta.patchtext/x-patch; charset=US-ASCII; name=v1-0001-jsonb_plperl-jsonb_plpython-Add-missing-check_sta.patchDownload+10-1
reproduce_circular_ref.pytext/x-python; charset=US-ASCII; name=reproduce_circular_ref.pyDownload
reproduce_stack_overflow.pytext/x-python; charset=US-ASCII; name=reproduce_stack_overflow.pyDownload
#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Aleksander Alekseev (#1)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Aleksander Alekseev <aleksander@tigerdata.com> writes:

I discovered several bugs in jsonb_plperl and jsonb_plpython.

Good catches, will take care of this.

(I'm inclined to fold these into one patch, though.)

regards, tom lane

#3Tom Lane
tgl@sss.pgh.pa.us
In reply to: Aleksander Alekseev (#1)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Aleksander Alekseev <aleksander@tigerdata.com> writes:

The second bug affects only jsonb_plperl. It's possible to construct a
Perl object with circular references which will cause
SV_to_JsonbValue() to go into an infinite loop here:

while (SvROK(in))
in = SvRV(in);

I suggest fixing it by rewriting the while loop into a recursion with
check_stack_depth() call. This will make the behavior consistent with
jsonb_plpython.

Unfortunately, your 0002 is too cute for its own good. I tried it
here, with a not-especially-new gcc compiling at -O2, and found that
the tail recursion in SV_deref() is optimized into a loop. So the
stack doesn't grow and we still have an uninterruptible loop.

I don't immediately see a way to write that function so that the
compiler is certain not to spot the tail recursion. Tricks like
two mutually recursive functions might be seen through at
sufficiently high -O levels.

We could instead add a CHECK_FOR_INTERRUPTS, so that you can at
least break out of the infinite loop. I'm not sure if the case
is worth more effort than that.

regards, tom lane

#4Aleksander Alekseev
aleksander@timescale.com
In reply to: Tom Lane (#3)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Hi Tom,

Thanks for taking a look.

The second bug affects only jsonb_plperl. It's possible to construct a
Perl object with circular references which will cause
SV_to_JsonbValue() to go into an infinite loop here:

while (SvROK(in))
in = SvRV(in);

I suggest fixing it by rewriting the while loop into a recursion with
check_stack_depth() call. This will make the behavior consistent with
jsonb_plpython.

Unfortunately, your 0002 is too cute for its own good. I tried it
here, with a not-especially-new gcc compiling at -O2, and found that
the tail recursion in SV_deref() is optimized into a loop. So the
stack doesn't grow and we still have an uninterruptible loop.

That's funny :)

I don't immediately see a way to write that function so that the
compiler is certain not to spot the tail recursion. Tricks like
two mutually recursive functions might be seen through at
sufficiently high -O levels.

We could instead add a CHECK_FOR_INTERRUPTS, so that you can at
least break out of the infinite loop. I'm not sure if the case
is worth more effort than that.

Alternatively I could implement a check for the loops, if you feel
like this is worth the effort.

--
Best regards,
Aleksander Alekseev

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Aleksander Alekseev (#4)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Aleksander Alekseev <aleksander@tigerdata.com> writes:

We could instead add a CHECK_FOR_INTERRUPTS, so that you can at
least break out of the infinite loop. I'm not sure if the case
is worth more effort than that.

Alternatively I could implement a check for the loops, if you feel
like this is worth the effort.

I thought about that, but I'm not sure how to build a bulletproof
check at reasonable (ie, near zero) cost. We could detect the example
case where an object refers directly to itself, by noticing that "in"
doesn't change in one iteration. But I'm pretty sure it's possible to
build reference loops involving two or more Perl objects, and those
would fool such a check.

regards, tom lane

#6Aleksander Alekseev
aleksander@timescale.com
In reply to: Tom Lane (#5)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Hi,

Alternatively I could implement a check for the loops, if you feel
like this is worth the effort.

I thought about that, but I'm not sure how to build a bulletproof
check at reasonable (ie, near zero) cost. We could detect the example
case where an object refers directly to itself, by noticing that "in"
doesn't change in one iteration. But I'm pretty sure it's possible to
build reference loops involving two or more Perl objects, and those
would fool such a check.

I was thinking about depth-first search where we store our current
path in a set. If the visited node is already in the set then the
graph has loops.

This is not exactly cheap but the complexity is proportional to the
cost of the serialization so I think we should be fine. The good thing
is that the user will get a sensible error message instead of an
infinite loop.

--
Best regards,
Aleksander Alekseev

#7Tom Lane
tgl@sss.pgh.pa.us
In reply to: Aleksander Alekseev (#6)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Aleksander Alekseev <aleksander@tigerdata.com> writes:

I thought about that, but I'm not sure how to build a bulletproof
check at reasonable (ie, near zero) cost. We could detect the example
case where an object refers directly to itself, by noticing that "in"
doesn't change in one iteration. But I'm pretty sure it's possible to
build reference loops involving two or more Perl objects, and those
would fool such a check.

I was thinking about depth-first search where we store our current
path in a set. If the visited node is already in the set then the
graph has loops.

This is not exactly cheap but the complexity is proportional to the
cost of the serialization so I think we should be fine.

No, it'd be O(N^2) for an N-deep reference chain. Admittedly,
realistic use-cases would never have more than a couple of layers of
indirection. But this whole exercise is to guard against adversarial
inputs, I think. I don't really want to add cycles and complexity to
make our behavior a bit more friendly in cases that nobody is going
to get into unless they are trying to break the database.

regards, tom lane

#8Aleksander Alekseev
aleksander@timescale.com
In reply to: Tom Lane (#7)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Hi Tom,

Thanks for all your great feedback.

No, it'd be O(N^2) for an N-deep reference chain. Admittedly,
realistic use-cases would never have more than a couple of layers of
indirection. But this whole exercise is to guard against adversarial
inputs, I think. I don't really want to add cycles and complexity to
make our behavior a bit more friendly in cases that nobody is going
to get into unless they are trying to break the database.

OK, that's fair enough. Here is the corrected patch.

```
=# SELECT perl_circular();
^CCancel request sent
ERROR: canceling statement due to user request
CONTEXT: PL/Perl function "perl_circular"
```

--
Best regards,
Aleksander Alekseev

Attachments:

v2-0001-jsonb_plperl-jsonb_plpython-Fix-unguarded-recursi.patchtext/x-patch; charset=US-ASCII; name=v2-0001-jsonb_plperl-jsonb_plpython-Fix-unguarded-recursi.patchDownload+13-1
#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Aleksander Alekseev (#8)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Aleksander Alekseev <aleksander@tigerdata.com> writes:

OK, that's fair enough. Here is the corrected patch.

Pushed with some comments added.

regards, tom lane

#10Aleksander Alekseev
aleksander@timescale.com
In reply to: Tom Lane (#9)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Hi Tom,

Pushed with some comments added.

Thanks for applying the patch.

I looked around and found the same bug in hstore_plperl:

```
=# CREATE EXTENSION IF NOT EXISTS hstore_plperl CASCADE;
CREATE EXTENSION

=# CREATE OR REPLACE FUNCTION hstore_circular() RETURNS hstore
LANGUAGE plperl TRANSFORM FOR TYPE hstore AS $$
my $x;
$x = \$x;
return $x;
$$;
CREATE FUNCTION

=# SELECT hstore_circular();
^CCancel request sent
^CCancel request sent
^CCancel request sent
```

From what I can tell this is the last issue like this. Here is the patch.

Would you like to merge it as well or will it be better to start a new
thread for visibility?

--
Best regards,
Aleksander Alekseev

Attachments:

v1-0001-hstore_plperl-Add-CHECK_FOR_INTERRUPTS-in-referen.patchtext/x-patch; charset=US-ASCII; name=v1-0001-hstore_plperl-Add-CHECK_FOR_INTERRUPTS-in-referen.patchDownload+4-1
#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Aleksander Alekseev (#10)
Re: [PATCH] Fix segmentation fault and infinite loop in jsonb_{plperl,plpython}

Aleksander Alekseev <aleksander@tigerdata.com> writes:

I looked around and found the same bug in hstore_plperl:

D'oh, I should have spotted that myself. Pushed, thanks for noticing.

regards, tom lane