Thread: BUG #17908: plpython_to_hstore() crashes with a non-dict argument

BUG #17908: plpython_to_hstore() crashes with a non-dict argument

From
PG Bug reporting form
Date:
The following bug has been logged on the website:

Bug reference:      17908
Logged by:          Alexander Lakhin
Email address:      exclusion@gmail.com
PostgreSQL version: 15.2
Operating system:   Ubuntu 22.04
Description:

When executing the following query:
CREATE EXTENSION hstore_plpython3u CASCADE;
CREATE FUNCTION test_transform() RETURNS hstore
LANGUAGE plpython3u
TRANSFORM FOR TYPE hstore AS 'return "a"';
SELECT test_transform();

The server crashes with the stack trace:
Core was generated by `postgres: law regression [local] SELECT
                        '.
Program terminated with signal SIGABRT, Aborted.

warning: Section `.reg-xstate/3829693' in core file too small.
#0  __pthread_kill_implementation (no_tid=0, signo=6,
threadid=140451453683648) at ./nptl/pthread_kill.c:44
44      ./nptl/pthread_kill.c: No such file or directory.
(gdb) bt
#0  __pthread_kill_implementation (no_tid=0, signo=6,
threadid=140451453683648) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=140451453683648) at
./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (threadid=140451453683648, signo=signo@entry=6) at
./nptl/pthread_kill.c:89
#3  0x00007fbd66427476 in __GI_raise (sig=sig@entry=6) at
../sysdeps/posix/raise.c:26
#4  0x00007fbd6640d7f3 in __GI_abort () at ./stdlib/abort.c:79
#5  0x000055eb85f693d7 in __sanitizer::Abort() ()
#6  0x000055eb85f67271 in __sanitizer::Die() ()
#7  0x000055eb85f49707 in
__asan::ScopedInErrorReport::~ScopedInErrorReport() ()
#8  0x000055eb85f490b6 in
__asan::ReportDeadlySignal(__sanitizer::SignalContext const&) ()
#9  0x000055eb85f48702 in __asan::AsanOnDeadlySignal(int, void*, void*) ()
#10 <signal handler called>
#11 0x00007fbd1b2c15c8 in PyList_GetItem () from
/usr/lib/x86_64-linux-gnu/libpython3.10.so.1.0
#12 0x00007fbd6410ed7f in plpython_to_hstore (fcinfo=0x7ffdc25d0460) at
hstore_plpython.c:154
#13 0x000055eb88fcf3a6 in FunctionCall1Coll (flinfo=0x625000177268,
collation=0, arg1=140450178140016) at fmgr.c:1124
#14 0x00007fbd1b8194b4 in PLyObject_ToTransform (arg=0x625000177248,
plrv=0x7fbd1afa0770, isnull=0x625000184174, inarray=false) at
plpy_typeio.c:1124
#15 0x00007fbd1b80be9d in PLy_output_convert (arg=0x625000177248,
val=0x7fbd1afa0770, isnull=0x625000184174) at plpy_typeio.c:122
#16 0x00007fbd1b7e06fd in PLy_exec_function (fcinfo=0x625000184158,
proc=0x625000177220) at plpy_exec.c:235
#17 0x00007fbd1b7efa3a in plpython3_call_handler (fcinfo=0x625000184158) at
plpy_main.c:247
#18 0x000055eb872ab667 in ExecInterpExpr (state=0x625000184070,
econtext=0x625000183d70, isnull=0x7ffdc25d48a0) at execExprInterp.c:727
#19 0x000055eb872a3a4a in ExecInterpExprStillValid (state=0x625000184070,
econtext=0x625000183d70, isNull=0x7ffdc25d48a0) at execExprInterp.c:1826
#20 0x000055eb875a34cd in ExecEvalExprSwitchContext (state=0x625000184070,
econtext=0x625000183d70, isNull=0x7ffdc25d48a0) at
../../../src/include/executor/executor.h:341
#21 0x000055eb875a3024 in ExecProject (projInfo=0x625000184068) at
../../../src/include/executor/executor.h:375
#22 0x000055eb875a1f5c in ExecResult (pstate=0x625000183c58) at
nodeResult.c:136
#23 0x000055eb87376877 in ExecProcNodeFirst (node=0x625000183c58) at
execProcnode.c:464
#24 0x000055eb873393a4 in ExecProcNode (node=0x625000183c58) at
../../../src/include/executor/executor.h:259
#25 0x000055eb8731c58c in ExecutePlan (estate=0x625000183a20,
planstate=0x625000183c58, use_parallel_mode=false, operation=CMD_SELECT,
sendTuples=true, numberTuples=0, direction=ForwardScanDirection,
dest=0x625000181968, 
    execute_once=true) at execMain.c:1636
#26 0x000055eb8731bd92 in standard_ExecutorRun (queryDesc=0x6190000015a0,
direction=ForwardScanDirection, count=0, execute_once=true) at
execMain.c:363
#27 0x000055eb8731adb3 in ExecutorRun (queryDesc=0x6190000015a0,
direction=ForwardScanDirection, count=0, execute_once=true) at
execMain.c:307
#28 0x000055eb8843a4c9 in PortalRunSelect (portal=0x62500002aa20,
forward=true, count=0, dest=0x625000181968) at pquery.c:924
#29 0x000055eb8843887c in PortalRun (portal=0x62500002aa20,
count=9223372036854775807, isTopLevel=true, run_once=true,
dest=0x625000181968, altdest=0x625000181968, qc=0x7ffdc25d5900) at
pquery.c:768
#30 0x000055eb88422fff in exec_simple_query (query_string=0x625000005220
"SELECT test_transform();") at postgres.c:1250
#31 0x000055eb884205c9 in PostgresMain (dbname=0x629000011278 "regression",
username=0x629000011258 "law") at postgres.c:4593
#32 0x000055eb87ed06f1 in BackendRun (port=0x615000002b00) at
postmaster.c:4511
#33 0x000055eb87ece7f0 in BackendStartup (port=0x615000002b00) at
postmaster.c:4239
#34 0x000055eb87ec9c10 in ServerLoop () at postmaster.c:1806
#35 0x000055eb87ec41d7 in PostmasterMain (argc=3, argv=0x6030000006d0) at
postmaster.c:1478
#36 0x000055eb8770321b in main (argc=3, argv=0x6030000006d0) at main.c:202

(gdb) frame 12
#12 0x00007fbd6410ed7f in plpython_to_hstore (fcinfo=0x7ffdc25d0460) at
hstore_plpython.c:154
154                             tuple = PyList_GetItem(items, i);
(gdb) print items
$1 = (PyObject * volatile) 0x0

Here plpython_to_hstore() has got a string as an argument, but failed to
detect that it's not a dictionary.
With plpython2 (on REL_14_STABLE) I get:
ERROR:  not a Python mapping
CONTEXT:  while creating return value
PL/Python function "test_transform"

As noted in https://bugs.python.org/issue5945, the behaviour of
PyMapping_Check() changed in Python 3, so it can't be used anymore to
differentiate real dictionaries from strings or alike.


Re: BUG #17908: plpython_to_hstore() crashes with a non-dict argument

From
Dmitry Dolgov
Date:
> On Tue, Apr 25, 2023 at 01:00:02PM +0000, PG Bug reporting form wrote:
> The following bug has been logged on the website:
>
> Bug reference:      17908
> Logged by:          Alexander Lakhin
> Email address:      exclusion@gmail.com
> PostgreSQL version: 15.2
> Operating system:   Ubuntu 22.04
> Description:
>
> When executing the following query:
> CREATE EXTENSION hstore_plpython3u CASCADE;
> CREATE FUNCTION test_transform() RETURNS hstore
> LANGUAGE plpython3u
> TRANSFORM FOR TYPE hstore AS 'return "a"';
> SELECT test_transform();
>
> Here plpython_to_hstore() has got a string as an argument, but failed to
> detect that it's not a dictionary.
> With plpython2 (on REL_14_STABLE) I get:
> ERROR:  not a Python mapping
> CONTEXT:  while creating return value
> PL/Python function "test_transform"
>
> As noted in https://bugs.python.org/issue5945, the behaviour of
> PyMapping_Check() changed in Python 3, so it can't be used anymore to
> differentiate real dictionaries from strings or alike.

Thanks for finding this! That's indeed very annoying. After a quick
investigation looks like a proposed solution from [1] is to use
PyType_HasFeature with Py_TPFLAGS_MAPPING:

    -       if (!PyMapping_Check(dict))
    +       dict_type = Py_TYPE(dict);
    +       if (!PyType_HasFeature(dict_type, Py_TPFLAGS_MAPPING))

But this flag is available only starting from Python 3.10, so not
backward compatible :(

[1]: https://bugs.python.org/issue46376



Re: BUG #17908: plpython_to_hstore() crashes with a non-dict argument

From
Tom Lane
Date:
Dmitry Dolgov <9erthalion6@gmail.com> writes:
>> On Tue, Apr 25, 2023 at 01:00:02PM +0000, PG Bug reporting form wrote:
>> As noted in https://bugs.python.org/issue5945, the behaviour of
>> PyMapping_Check() changed in Python 3, so it can't be used anymore to
>> differentiate real dictionaries from strings or alike.

> Thanks for finding this! That's indeed very annoying.

Yuck.

> After a quick
> investigation looks like a proposed solution from [1] is to use
> PyType_HasFeature with Py_TPFLAGS_MAPPING:
>     -       if (!PyMapping_Check(dict))
>     +       dict_type = Py_TYPE(dict);
>     +       if (!PyType_HasFeature(dict_type, Py_TPFLAGS_MAPPING))
> But this flag is available only starting from Python 3.10, so not
> backward compatible :(

Yeah, doesn't sound hugely workable; I don't think we can require 3.10
yet.  The issue5945 discussion suggested

>>> And recommend `PyMapping_Check() && !PySequence_Check()` for true mapping test?

I haven't verified this but will go test it shortly.  If it's true,
it would explain why our other uses of PyMapping_Check haven't
failed, because (whether by chance or not I dunno) they are not
reached if PySequence_Check is true.

            regards, tom lane