mirror of
https://github.com/postgres/postgres.git
synced 2026-07-09 17:51:00 -04:00
plpython: Fix NULL pointer dereferences for broken sequence and mapping objects
PL/Python and its hstore and jsonb transforms build SQL values from Python containers by calling Python C API functions that can return NULL, and in several places the result was used without first checking it. On the sequence side, PySequence_GetItem() is used 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(). On the mapping side, the hstore and jsonb transforms call PyMapping_Size() and PyMapping_Items() and then index the result with PyList_GetItem() and PyTuple_GetItem(). All of these return NULL (or -1), with a Python exception set, for a broken object: for example one whose __getitem__() or items() raises, or which reports a length that disagrees with what it actually yields. The unchecked result was then dereferenced, crashing the backend. Fix this by checking the result of each call and reporting a regular error if it failed, so that the underlying Python exception is surfaced instead of taking down the session. Author: Richard Guo <guofenglinux@gmail.com> Reviewed-by: Ayush Tiwari <ayushtiwari.slg01@gmail.com> Discussion: https://postgr.es/m/CAMbWs49BKM9wP6m8bCXEpHwQKp7usvOGV6Jf=J7FYr_BCpxLqg@mail.gmail.com Backpatch-through: 14
This commit is contained in:
parent
e520ad34b4
commit
0b7719f744
16 changed files with 516 additions and 4 deletions
|
|
@ -43,6 +43,71 @@ SELECT test1bad();
|
|||
ERROR: not a Python mapping
|
||||
CONTEXT: while creating return value
|
||||
PL/Python function "test1bad"
|
||||
-- A mapping whose items() raises should be reported as an error, not crash
|
||||
-- the backend
|
||||
CREATE FUNCTION test1broken() RETURNS hstore
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
raise ValueError('items failed')
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
SELECT test1broken();
|
||||
ERROR: could not get items from Python mapping
|
||||
CONTEXT: while creating return value
|
||||
PL/Python function "test1broken"
|
||||
-- Likewise for a mapping whose items() does not return key/value pairs
|
||||
CREATE FUNCTION test1malformed() RETURNS hstore
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
return [42]
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
SELECT test1malformed();
|
||||
ERROR: items() of a Python mapping must return key/value pairs
|
||||
CONTEXT: while creating return value
|
||||
PL/Python function "test1malformed"
|
||||
-- Likewise for a mapping whose items() yields fewer pairs than its length
|
||||
CREATE FUNCTION test1short() RETURNS hstore
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
return []
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
SELECT test1short();
|
||||
ERROR: items() of a Python mapping must return key/value pairs
|
||||
CONTEXT: while creating return value
|
||||
PL/Python function "test1short"
|
||||
-- Likewise for a mapping whose __len__() raises
|
||||
CREATE FUNCTION test1brokenlen() RETURNS hstore
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
class C(dict):
|
||||
def __len__(self):
|
||||
raise ValueError('len failed')
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
SELECT test1brokenlen();
|
||||
ERROR: could not get size of Python mapping
|
||||
CONTEXT: while creating return value
|
||||
PL/Python function "test1brokenlen"
|
||||
-- test hstore[] -> python
|
||||
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
|
|
|
|||
|
|
@ -145,7 +145,16 @@ plpython_to_hstore(PG_FUNCTION_ARGS)
|
|||
errmsg("not a Python mapping")));
|
||||
|
||||
pcount = PyMapping_Size(dict);
|
||||
if (pcount < 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("could not get size of Python mapping")));
|
||||
|
||||
items = PyMapping_Items(dict);
|
||||
if (items == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("could not get items from Python mapping")));
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
|
|
@ -162,6 +171,13 @@ plpython_to_hstore(PG_FUNCTION_ARGS)
|
|||
PyObject *value;
|
||||
|
||||
tuple = PyList_GetItem(items, i);
|
||||
|
||||
/* The mapping's items() must yield key/value pairs */
|
||||
if (tuple == NULL || !PyTuple_Check(tuple) || PyTuple_Size(tuple) < 2)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("items() of a Python mapping must return key/value pairs")));
|
||||
|
||||
key = PyTuple_GetItem(tuple, 0);
|
||||
value = PyTuple_GetItem(tuple, 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,71 @@ $$;
|
|||
SELECT test1bad();
|
||||
|
||||
|
||||
-- A mapping whose items() raises should be reported as an error, not crash
|
||||
-- the backend
|
||||
CREATE FUNCTION test1broken() RETURNS hstore
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
raise ValueError('items failed')
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
|
||||
SELECT test1broken();
|
||||
|
||||
|
||||
-- Likewise for a mapping whose items() does not return key/value pairs
|
||||
CREATE FUNCTION test1malformed() RETURNS hstore
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
return [42]
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
|
||||
SELECT test1malformed();
|
||||
|
||||
|
||||
-- Likewise for a mapping whose items() yields fewer pairs than its length
|
||||
CREATE FUNCTION test1short() RETURNS hstore
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
return []
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
|
||||
SELECT test1short();
|
||||
|
||||
|
||||
-- Likewise for a mapping whose __len__() raises
|
||||
CREATE FUNCTION test1brokenlen() RETURNS hstore
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE hstore
|
||||
AS $$
|
||||
class C(dict):
|
||||
def __len__(self):
|
||||
raise ValueError('len failed')
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
|
||||
SELECT test1brokenlen();
|
||||
|
||||
|
||||
-- test hstore[] -> python
|
||||
CREATE FUNCTION test1arr(val hstore[]) RETURNS int
|
||||
LANGUAGE plpythonu
|
||||
|
|
|
|||
|
|
@ -304,3 +304,92 @@ 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"
|
||||
-- A mapping whose items() raises should be reported as an error, not crash
|
||||
-- the backend
|
||||
CREATE FUNCTION test_broken_mapping() RETURNS jsonb
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE jsonb
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
raise ValueError('items failed')
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
SELECT test_broken_mapping();
|
||||
ERROR: could not get items from Python mapping
|
||||
DETAIL: ValueError: items failed
|
||||
CONTEXT: Traceback (most recent call last):
|
||||
while creating return value
|
||||
PL/Python function "test_broken_mapping"
|
||||
-- Likewise for a mapping whose items() does not return key/value pairs
|
||||
CREATE FUNCTION test_malformed_mapping() RETURNS jsonb
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE jsonb
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
return [42]
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
SELECT test_malformed_mapping();
|
||||
ERROR: items() of a Python mapping must return key/value pairs
|
||||
CONTEXT: while creating return value
|
||||
PL/Python function "test_malformed_mapping"
|
||||
-- Likewise for a mapping whose items() yields fewer pairs than its length
|
||||
CREATE FUNCTION test_short_mapping() RETURNS jsonb
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE jsonb
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
return []
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
SELECT test_short_mapping();
|
||||
ERROR: items() of a Python mapping must return key/value pairs
|
||||
DETAIL: IndexError: list index out of range
|
||||
CONTEXT: while creating return value
|
||||
PL/Python function "test_short_mapping"
|
||||
-- Likewise for a mapping whose __len__() raises
|
||||
CREATE FUNCTION test_broken_len_mapping() RETURNS jsonb
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE jsonb
|
||||
AS $$
|
||||
class C(dict):
|
||||
def __len__(self):
|
||||
raise ValueError('len failed')
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
SELECT test_broken_len_mapping();
|
||||
ERROR: could not get size of Python mapping
|
||||
DETAIL: ValueError: len failed
|
||||
CONTEXT: Traceback (most recent call last):
|
||||
while creating return value
|
||||
PL/Python function "test_broken_len_mapping"
|
||||
|
|
|
|||
|
|
@ -277,7 +277,12 @@ PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
|
|||
JsonbValue *volatile out;
|
||||
|
||||
pcount = PyMapping_Size(obj);
|
||||
if (pcount < 0)
|
||||
PLy_elog(ERROR, "could not get size of Python mapping");
|
||||
|
||||
items = PyMapping_Items(obj);
|
||||
if (items == NULL)
|
||||
PLy_elog(ERROR, "could not get items from Python mapping");
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
|
|
@ -289,8 +294,15 @@ PLyMapping_ToJsonbValue(PyObject *obj, JsonbParseState **jsonb_state)
|
|||
{
|
||||
JsonbValue jbvKey;
|
||||
PyObject *item = PyList_GetItem(items, i);
|
||||
PyObject *key = PyTuple_GetItem(item, 0);
|
||||
PyObject *value = PyTuple_GetItem(item, 1);
|
||||
PyObject *key;
|
||||
PyObject *value;
|
||||
|
||||
/* The mapping's items() must yield key/value pairs */
|
||||
if (item == NULL || !PyTuple_Check(item) || PyTuple_Size(item) < 2)
|
||||
PLy_elog(ERROR, "items() of a Python mapping must return key/value pairs");
|
||||
|
||||
key = PyTuple_GetItem(item, 0);
|
||||
value = PyTuple_GetItem(item, 1);
|
||||
|
||||
/* Python dictionary can have None as key */
|
||||
if (key == Py_None)
|
||||
|
|
@ -342,7 +354,10 @@ PLySequence_ToJsonbValue(PyObject *obj, JsonbParseState **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);
|
||||
|
||||
(void) PLyObject_ToJsonbValue(value, jsonb_state, true);
|
||||
Py_XDECREF(value);
|
||||
|
|
|
|||
|
|
@ -181,3 +181,80 @@ 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();
|
||||
|
||||
-- A mapping whose items() raises should be reported as an error, not crash
|
||||
-- the backend
|
||||
CREATE FUNCTION test_broken_mapping() RETURNS jsonb
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE jsonb
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
raise ValueError('items failed')
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
|
||||
SELECT test_broken_mapping();
|
||||
|
||||
-- Likewise for a mapping whose items() does not return key/value pairs
|
||||
CREATE FUNCTION test_malformed_mapping() RETURNS jsonb
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE jsonb
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
return [42]
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
|
||||
SELECT test_malformed_mapping();
|
||||
|
||||
-- Likewise for a mapping whose items() yields fewer pairs than its length
|
||||
CREATE FUNCTION test_short_mapping() RETURNS jsonb
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE jsonb
|
||||
AS $$
|
||||
class C(dict):
|
||||
def items(self):
|
||||
return []
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
|
||||
SELECT test_short_mapping();
|
||||
|
||||
-- Likewise for a mapping whose __len__() raises
|
||||
CREATE FUNCTION test_broken_len_mapping() RETURNS jsonb
|
||||
LANGUAGE plpython3u
|
||||
TRANSFORM FOR TYPE jsonb
|
||||
AS $$
|
||||
class C(dict):
|
||||
def __len__(self):
|
||||
raise ValueError('len failed')
|
||||
d = C()
|
||||
d['x'] = 1
|
||||
return d
|
||||
$$;
|
||||
|
||||
SELECT test_broken_len_mapping();
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -464,3 +464,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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
--
|
||||
|
|
|
|||
|
|
@ -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
|
||||
--
|
||||
|
|
|
|||
|
|
@ -231,6 +231,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();
|
||||
{
|
||||
bool isnull;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,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 (PyString_Check(optr))
|
||||
sptr = PyString_AsString(optr);
|
||||
else if (PyUnicode_Check(optr))
|
||||
|
|
@ -254,6 +259,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();
|
||||
{
|
||||
bool isnull;
|
||||
|
|
|
|||
|
|
@ -1214,6 +1214,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();
|
||||
{
|
||||
|
|
@ -1460,7 +1464,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);
|
||||
|
||||
|
|
|
|||
|
|
@ -233,3 +233,15 @@ CREATE FUNCTION composite_type_as_list_broken() RETURNS type_record[] AS $$
|
|||
return [['first', 1]];
|
||||
$$ LANGUAGE plpythonu;
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -320,3 +320,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();
|
||||
|
|
|
|||
|
|
@ -417,6 +417,19 @@ $$ LANGUAGE plpythonu;
|
|||
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue