v1-0001-plpython-Fix-NULL-pointer-dereference-for-broken-.patch
application/octet-stream
Filename: v1-0001-plpython-Fix-NULL-pointer-dereference-for-broken-.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch v1-0001
Subject: plpython: Fix NULL pointer dereference for broken sequence objects
| File | + | − |
|---|---|---|
| contrib/jsonb_plpython/expected/jsonb_plpython.out | 19 | 0 |
| contrib/jsonb_plpython/jsonb_plpython.c | 4 | 1 |
| contrib/jsonb_plpython/sql/jsonb_plpython.sql | 16 | 0 |
| src/pl/plpython/expected/plpython_composite.out | 16 | 0 |
| src/pl/plpython/expected/plpython_spi.out | 51 | 0 |
| src/pl/plpython/expected/plpython_types.out | 16 | 0 |
| src/pl/plpython/plpy_cursorobject.c | 5 | 0 |
| src/pl/plpython/plpy_spi.c | 10 | 0 |
| src/pl/plpython/plpy_typeio.c | 8 | 1 |
| src/pl/plpython/sql/plpython_composite.sql | 12 | 0 |
| src/pl/plpython/sql/plpython_spi.sql | 39 | 0 |
| src/pl/plpython/sql/plpython_types.sql | 13 | 0 |
From 49b5c015dcd05b9fd96bf6ef9320bacf4f4acd67 Mon Sep 17 00:00:00 2001
From: Richard Guo <guofenglinux@gmail.com>
Date: Thu, 25 Jun 2026 16:30:50 +0900
Subject: [PATCH v1] plpython: Fix NULL pointer dereference for broken sequence
objects
In several places PL/Python fetches the elements of a Python sequence
with PySequence_GetItem() and then uses the returned object without
first checking it for NULL. This is done when converting a returned
sequence into a SQL array or composite value, when reading the
argument list passed to plpy.execute() or plpy.cursor(), and when
reading the list of type names given to plpy.prepare(). The
jsonb_plpython transform does the same while building a jsonb array.
PySequence_GetItem() returns NULL, with a Python exception set, when
an element cannot be retrieved, and the unchecked dereference that
follows crashes the backend.
Fix this by checking the result of PySequence_GetItem() in each of
these places and reporting a regular error if it is NULL, so that the
underlying Python exception is surfaced instead of taking down the
session.
---
.../expected/jsonb_plpython.out | 19 +++++++
contrib/jsonb_plpython/jsonb_plpython.c | 5 +-
contrib/jsonb_plpython/sql/jsonb_plpython.sql | 16 ++++++
.../plpython/expected/plpython_composite.out | 16 ++++++
src/pl/plpython/expected/plpython_spi.out | 51 +++++++++++++++++++
src/pl/plpython/expected/plpython_types.out | 16 ++++++
src/pl/plpython/plpy_cursorobject.c | 5 ++
src/pl/plpython/plpy_spi.c | 10 ++++
src/pl/plpython/plpy_typeio.c | 9 +++-
src/pl/plpython/sql/plpython_composite.sql | 12 +++++
src/pl/plpython/sql/plpython_spi.sql | 39 ++++++++++++++
src/pl/plpython/sql/plpython_types.sql | 13 +++++
12 files changed, 209 insertions(+), 2 deletions(-)
diff --git a/contrib/jsonb_plpython/expected/jsonb_plpython.out b/contrib/jsonb_plpython/expected/jsonb_plpython.out
index cac963de69c..0b32583f5a5 100644
--- a/contrib/jsonb_plpython/expected/jsonb_plpython.out
+++ b/contrib/jsonb_plpython/expected/jsonb_plpython.out
@@ -304,3 +304,22 @@ SELECT test_dict1();
{"": 2, "a": 1, "33": 3}
(1 row)
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend
+CREATE FUNCTION test_broken_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$;
+SELECT test_broken_sequence();
+ERROR: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+while creating return value
+PL/Python function "test_broken_sequence"
diff --git a/contrib/jsonb_plpython/jsonb_plpython.c b/contrib/jsonb_plpython/jsonb_plpython.c
index 909612a6039..d27350589ea 100644
--- a/contrib/jsonb_plpython/jsonb_plpython.c
+++ b/contrib/jsonb_plpython/jsonb_plpython.c
@@ -337,7 +337,10 @@ PLySequence_ToJsonbValue(PyObject *obj, JsonbInState *jsonb_state)
for (i = 0; i < pcount; i++)
{
value = PySequence_GetItem(obj, i);
- Assert(value);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (value == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", (int) i);
PLyObject_ToJsonbValue(value, jsonb_state, true);
Py_XDECREF(value);
diff --git a/contrib/jsonb_plpython/sql/jsonb_plpython.sql b/contrib/jsonb_plpython/sql/jsonb_plpython.sql
index 29dc33279a0..44745e645f1 100644
--- a/contrib/jsonb_plpython/sql/jsonb_plpython.sql
+++ b/contrib/jsonb_plpython/sql/jsonb_plpython.sql
@@ -181,3 +181,19 @@ return x
$$;
SELECT test_dict1();
+
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend
+CREATE FUNCTION test_broken_sequence() RETURNS jsonb
+LANGUAGE plpython3u
+TRANSFORM FOR TYPE jsonb
+AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$;
+
+SELECT test_broken_sequence();
diff --git a/src/pl/plpython/expected/plpython_composite.out b/src/pl/plpython/expected/plpython_composite.out
index 674af93ddcf..ffce7fc1be7 100644
--- a/src/pl/plpython/expected/plpython_composite.out
+++ b/src/pl/plpython/expected/plpython_composite.out
@@ -606,3 +606,19 @@ DETAIL: Missing left parenthesis.
HINT: To return a composite type in an array, return the composite type as a Python tuple, e.g., "[('foo',)]".
CONTEXT: while creating return value
PL/Python function "composite_type_as_list_broken"
+-- A custom sequence whose length matches the tuple but whose __getitem__
+-- raises should be reported as an error, not crash the backend.
+CREATE FUNCTION composite_type_as_broken_sequence() RETURNS type_record AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$ LANGUAGE plpython3u;
+SELECT * FROM composite_type_as_broken_sequence();
+ERROR: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+while creating return value
+PL/Python function "composite_type_as_broken_sequence"
diff --git a/src/pl/plpython/expected/plpython_spi.out b/src/pl/plpython/expected/plpython_spi.out
index b572f9bf73b..0320ff01f6b 100644
--- a/src/pl/plpython/expected/plpython_spi.out
+++ b/src/pl/plpython/expected/plpython_spi.out
@@ -451,3 +451,54 @@ SELECT plan_composite_args();
(3,label)
(1 row)
+-- A custom argument sequence whose length matches the plan but whose
+-- __getitem__ raises should be reported as an error, not crash the backend.
+CREATE FUNCTION plan_broken_arg_sequence() RETURNS void AS $$
+plan = plpy.prepare("select $1", ["int4"])
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.execute(plan, C())
+$$ LANGUAGE plpython3u;
+SELECT plan_broken_arg_sequence();
+ERROR: spiexceptions.ExternalRoutineException: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "plan_broken_arg_sequence", line 8, in <module>
+ plpy.execute(plan, C())
+PL/Python function "plan_broken_arg_sequence"
+-- Likewise for the type-name list passed to plpy.prepare().
+CREATE FUNCTION prepare_broken_type_sequence() RETURNS void AS $$
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.prepare("select $1", C())
+$$ LANGUAGE plpython3u;
+SELECT prepare_broken_type_sequence();
+ERROR: spiexceptions.ExternalRoutineException: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "prepare_broken_type_sequence", line 7, in <module>
+ plpy.prepare("select $1", C())
+PL/Python function "prepare_broken_type_sequence"
+-- Likewise for the argument sequence passed to plpy.cursor().
+CREATE FUNCTION cursor_broken_arg_sequence() RETURNS void AS $$
+plan = plpy.prepare("select $1", ["int4"])
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.cursor(plan, C())
+$$ LANGUAGE plpython3u;
+SELECT cursor_broken_arg_sequence();
+ERROR: spiexceptions.ExternalRoutineException: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "cursor_broken_arg_sequence", line 8, in <module>
+ plpy.cursor(plan, C())
+PL/Python function "cursor_broken_arg_sequence"
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
index 8a680e15c14..0cb3d6ea8c6 100644
--- a/src/pl/plpython/expected/plpython_types.out
+++ b/src/pl/plpython/expected/plpython_types.out
@@ -796,6 +796,22 @@ SELECT * FROM test_type_conversion_array_error();
ERROR: return value of function with array return type is not a Python sequence
CONTEXT: while creating return value
PL/Python function "test_type_conversion_array_error"
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend.
+CREATE FUNCTION test_type_conversion_array_getitem_fail() RETURNS int[] AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_getitem_fail();
+ERROR: could not get element 0 from sequence
+DETAIL: ValueError: getitem failed
+CONTEXT: Traceback (most recent call last):
+while creating return value
+PL/Python function "test_type_conversion_array_getitem_fail"
--
-- Domains over arrays
--
diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c
index cc74c4df6ba..0725fbc19f2 100644
--- a/src/pl/plpython/plpy_cursorobject.c
+++ b/src/pl/plpython/plpy_cursorobject.c
@@ -258,6 +258,11 @@ PLy_cursor_plan(PyObject *ob, PyObject *args)
PyObject *elem;
elem = PySequence_GetItem(args, j);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (elem == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", j);
+
PG_TRY(2);
{
bool isnull;
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index 4ad40bf78f3..4980873efcf 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -87,6 +87,11 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
int32 typmod;
optr = PySequence_GetItem(list, i);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (optr == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", i);
+
if (PyUnicode_Check(optr))
sptr = PLyUnicode_AsString(optr);
else
@@ -250,6 +255,11 @@ PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
PyObject *elem;
elem = PySequence_GetItem(list, j);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (elem == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", j);
+
PG_TRY(2);
{
bool isnull;
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index 92d55bf9f42..e499ed8cbfd 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -1210,6 +1210,10 @@ PLySequence_ToArray_recurse(PyObject *obj, ArrayBuildState **astatep,
/* fetch the array element */
PyObject *subobj = PySequence_GetItem(obj, i);
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (subobj == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", i);
+
/* need PG_TRY to ensure we release the subobj's refcount */
PG_TRY();
{
@@ -1456,7 +1460,10 @@ PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence)
PG_TRY();
{
value = PySequence_GetItem(sequence, idx);
- Assert(value);
+
+ /* PySequence_GetItem() can return NULL, with an exception set */
+ if (value == NULL)
+ PLy_elog(ERROR, "could not get element %d from sequence", idx);
values[i] = att->func(att, value, &nulls[i], false);
diff --git a/src/pl/plpython/sql/plpython_composite.sql b/src/pl/plpython/sql/plpython_composite.sql
index 1bb9b83b719..b401b3f2f6b 100644
--- a/src/pl/plpython/sql/plpython_composite.sql
+++ b/src/pl/plpython/sql/plpython_composite.sql
@@ -233,3 +233,15 @@ CREATE FUNCTION composite_type_as_list_broken() RETURNS type_record[] AS $$
return [['first', 1]];
$$ LANGUAGE plpython3u;
SELECT * FROM composite_type_as_list_broken();
+
+-- A custom sequence whose length matches the tuple but whose __getitem__
+-- raises should be reported as an error, not crash the backend.
+CREATE FUNCTION composite_type_as_broken_sequence() RETURNS type_record AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$ LANGUAGE plpython3u;
+SELECT * FROM composite_type_as_broken_sequence();
diff --git a/src/pl/plpython/sql/plpython_spi.sql b/src/pl/plpython/sql/plpython_spi.sql
index 00dcc8bb669..276d130431e 100644
--- a/src/pl/plpython/sql/plpython_spi.sql
+++ b/src/pl/plpython/sql/plpython_spi.sql
@@ -307,3 +307,42 @@ SELECT cursor_fetch_next_empty();
SELECT cursor_plan();
SELECT cursor_plan_wrong_args();
SELECT plan_composite_args();
+
+-- A custom argument sequence whose length matches the plan but whose
+-- __getitem__ raises should be reported as an error, not crash the backend.
+CREATE FUNCTION plan_broken_arg_sequence() RETURNS void AS $$
+plan = plpy.prepare("select $1", ["int4"])
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.execute(plan, C())
+$$ LANGUAGE plpython3u;
+
+SELECT plan_broken_arg_sequence();
+
+-- Likewise for the type-name list passed to plpy.prepare().
+CREATE FUNCTION prepare_broken_type_sequence() RETURNS void AS $$
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.prepare("select $1", C())
+$$ LANGUAGE plpython3u;
+
+SELECT prepare_broken_type_sequence();
+
+-- Likewise for the argument sequence passed to plpy.cursor().
+CREATE FUNCTION cursor_broken_arg_sequence() RETURNS void AS $$
+plan = plpy.prepare("select $1", ["int4"])
+class C:
+ def __len__(self):
+ return 1
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+plpy.cursor(plan, C())
+$$ LANGUAGE plpython3u;
+
+SELECT cursor_broken_arg_sequence();
diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql
index 0985a9cca2f..31549c7f4f1 100644
--- a/src/pl/plpython/sql/plpython_types.sql
+++ b/src/pl/plpython/sql/plpython_types.sql
@@ -417,6 +417,19 @@ $$ LANGUAGE plpython3u;
SELECT * FROM test_type_conversion_array_error();
+-- A custom sequence whose __getitem__ raises should be reported as an error,
+-- not crash the backend.
+CREATE FUNCTION test_type_conversion_array_getitem_fail() RETURNS int[] AS $$
+class C:
+ def __len__(self):
+ return 2
+ def __getitem__(self, i):
+ raise ValueError('getitem failed')
+return C()
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_getitem_fail();
+
--
-- Domains over arrays
--
2.39.5 (Apple Git-154)