From f469f634ad7338b7eab046238354f07930fb6bca Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Fri, 21 Aug 2015 12:21:37 -0400 Subject: [PATCH] Fix plpython crash when returning string representation of a RECORD result. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PLyString_ToComposite() blithely overwrote proc->result.out.d, even though for a composite result type the other union variant proc->result.out.r is the one that should be valid. This could result in a crash if out.r had in fact been filled in (proc->result.is_rowtype == 1) and then somebody later attempted to use that data; as per bug #13579 from Paweł Michalak. Just to add insult to injury, it didn't work for RECORD results anyway, because record_in() would refuse the case. Fix by doing the I/O function lookup in a local PLyTypeInfo variable, as we were doing already in PLyObject_ToComposite(). This is not a great technique because any fn_extra data allocated by the input function will be leaked permanently (thanks to using TopMemoryContext as fn_mcxt). But that's a pre-existing issue that is much less serious than a crash, so leave it to be fixed separately. This bug would be a potential security issue, except that plpython is only available to superusers and the crash requires coding the function in a way that didn't work before today's patches. Add regression test cases covering all the supported methods of converting composite results. Back-patch to 9.1 where the faulty coding was introduced. --- .../plpython/expected/plpython_composite.out | 206 ++++++++++++++++++ src/pl/plpython/plpy_typeio.c | 14 +- src/pl/plpython/sql/plpython_composite.sql | 41 ++++ 3 files changed, 259 insertions(+), 2 deletions(-) diff --git a/src/pl/plpython/expected/plpython_composite.out b/src/pl/plpython/expected/plpython_composite.out index ad454f37394..0ef0c21235c 100644 --- a/src/pl/plpython/expected/plpython_composite.out +++ b/src/pl/plpython/expected/plpython_composite.out @@ -65,6 +65,8 @@ elif typ == 'obj': type_record.first = first type_record.second = second return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) $$ LANGUAGE plpythonu; SELECT * FROM multiout_record_as('dict', 'foo', 1, 'f'); first | second @@ -78,6 +80,138 @@ SELECT multiout_record_as('dict', 'foo', 1, 'f'); (foo,1) (1 row) +SELECT * FROM multiout_record_as('dict', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('dict', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('dict', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('dict', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('dict', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('tuple', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('tuple', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('tuple', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('tuple', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('tuple', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('list', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('list', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('list', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('list', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('list', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('obj', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('obj', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('obj', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('obj', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('obj', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('str', 'one', 1, false); + first | second +-------+-------- + 'one' | 1 +(1 row) + +SELECT * FROM multiout_record_as('str', 'one', 2, false); + first | second +-------+-------- + 'one' | 2 +(1 row) + SELECT *, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s); f | s | snull -----+---+------- @@ -179,10 +313,14 @@ elif typ == 'dict': d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'} elif typ == 'tuple': d = (n * 2, n * 3) +elif typ == 'list': + d = [ n * 2, n * 3 ] elif typ == 'obj': class d: pass d.first = n * 2 d.second = n * 3 +elif typ == 'str': + d = "(%r,%r)" % (n * 2, n * 3) for i in range(n): yield (i, d) $$ LANGUAGE plpythonu; @@ -200,6 +338,18 @@ SELECT * FROM multiout_table_type_setof('dict', 'f', 3); 2 | (6,9) (3 rows) +SELECT * FROM multiout_table_type_setof('dict', 'f', 7); + n | column2 +---+--------- + 0 | (14,21) + 1 | (14,21) + 2 | (14,21) + 3 | (14,21) + 4 | (14,21) + 5 | (14,21) + 6 | (14,21) +(7 rows) + SELECT * FROM multiout_table_type_setof('tuple', 'f', 2); n | column2 ---+--------- @@ -207,6 +357,29 @@ SELECT * FROM multiout_table_type_setof('tuple', 'f', 2); 1 | (4,6) (2 rows) +SELECT * FROM multiout_table_type_setof('tuple', 'f', 3); + n | column2 +---+--------- + 0 | (6,9) + 1 | (6,9) + 2 | (6,9) +(3 rows) + +SELECT * FROM multiout_table_type_setof('list', 'f', 2); + n | column2 +---+--------- + 0 | (4,6) + 1 | (4,6) +(2 rows) + +SELECT * FROM multiout_table_type_setof('list', 'f', 3); + n | column2 +---+--------- + 0 | (6,9) + 1 | (6,9) + 2 | (6,9) +(3 rows) + SELECT * FROM multiout_table_type_setof('obj', 'f', 4); n | column2 ---+--------- @@ -216,6 +389,39 @@ SELECT * FROM multiout_table_type_setof('obj', 'f', 4); 3 | (8,12) (4 rows) +SELECT * FROM multiout_table_type_setof('obj', 'f', 5); + n | column2 +---+--------- + 0 | (10,15) + 1 | (10,15) + 2 | (10,15) + 3 | (10,15) + 4 | (10,15) +(5 rows) + +SELECT * FROM multiout_table_type_setof('str', 'f', 6); + n | column2 +---+--------- + 0 | (12,18) + 1 | (12,18) + 2 | (12,18) + 3 | (12,18) + 4 | (12,18) + 5 | (12,18) +(6 rows) + +SELECT * FROM multiout_table_type_setof('str', 'f', 7); + n | column2 +---+--------- + 0 | (14,21) + 1 | (14,21) + 2 | (14,21) + 3 | (14,21) + 4 | (14,21) + 5 | (14,21) + 6 | (14,21) +(7 rows) + SELECT * FROM multiout_table_type_setof('dict', 't', 3); n | column2 ---+--------- diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c index 7a45b228714..05add6e2ce8 100644 --- a/src/pl/plpython/plpy_typeio.c +++ b/src/pl/plpython/plpy_typeio.c @@ -912,20 +912,30 @@ PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv) static Datum PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string) { + Datum result; HeapTuple typeTup; + PLyTypeInfo locinfo; PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + /* Create a dummy PLyTypeInfo */ + MemSet(&locinfo, 0, sizeof(PLyTypeInfo)); + PLy_typeinfo_init(&locinfo); + typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid)); if (!HeapTupleIsValid(typeTup)) elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid); - PLy_output_datum_func2(&info->out.d, typeTup, + PLy_output_datum_func2(&locinfo.out.d, typeTup, exec_ctx->curr_proc->langid, exec_ctx->curr_proc->trftypes); ReleaseSysCache(typeTup); - return PLyObject_ToDatum(&info->out.d, info->out.d.typmod, string); + result = PLyObject_ToDatum(&locinfo.out.d, desc->tdtypmod, string); + + PLy_typeinfo_dealloc(&locinfo); + + return result; } diff --git a/src/pl/plpython/sql/plpython_composite.sql b/src/pl/plpython/sql/plpython_composite.sql index cb5fffeba9d..473342c2f40 100644 --- a/src/pl/plpython/sql/plpython_composite.sql +++ b/src/pl/plpython/sql/plpython_composite.sql @@ -32,10 +32,40 @@ elif typ == 'obj': type_record.first = first type_record.second = second return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) $$ LANGUAGE plpythonu; SELECT * FROM multiout_record_as('dict', 'foo', 1, 'f'); SELECT multiout_record_as('dict', 'foo', 1, 'f'); + +SELECT * FROM multiout_record_as('dict', null, null, false); +SELECT * FROM multiout_record_as('dict', 'one', null, false); +SELECT * FROM multiout_record_as('dict', null, 2, false); +SELECT * FROM multiout_record_as('dict', 'three', 3, false); +SELECT * FROM multiout_record_as('dict', null, null, true); + +SELECT * FROM multiout_record_as('tuple', null, null, false); +SELECT * FROM multiout_record_as('tuple', 'one', null, false); +SELECT * FROM multiout_record_as('tuple', null, 2, false); +SELECT * FROM multiout_record_as('tuple', 'three', 3, false); +SELECT * FROM multiout_record_as('tuple', null, null, true); + +SELECT * FROM multiout_record_as('list', null, null, false); +SELECT * FROM multiout_record_as('list', 'one', null, false); +SELECT * FROM multiout_record_as('list', null, 2, false); +SELECT * FROM multiout_record_as('list', 'three', 3, false); +SELECT * FROM multiout_record_as('list', null, null, true); + +SELECT * FROM multiout_record_as('obj', null, null, false); +SELECT * FROM multiout_record_as('obj', 'one', null, false); +SELECT * FROM multiout_record_as('obj', null, 2, false); +SELECT * FROM multiout_record_as('obj', 'three', 3, false); +SELECT * FROM multiout_record_as('obj', null, null, true); + +SELECT * FROM multiout_record_as('str', 'one', 1, false); +SELECT * FROM multiout_record_as('str', 'one', 2, false); + SELECT *, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s); SELECT *, f IS NULL AS fnull, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s); SELECT * FROM multiout_record_as('obj', NULL, 10, 'f'); @@ -92,18 +122,29 @@ elif typ == 'dict': d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'} elif typ == 'tuple': d = (n * 2, n * 3) +elif typ == 'list': + d = [ n * 2, n * 3 ] elif typ == 'obj': class d: pass d.first = n * 2 d.second = n * 3 +elif typ == 'str': + d = "(%r,%r)" % (n * 2, n * 3) for i in range(n): yield (i, d) $$ LANGUAGE plpythonu; SELECT * FROM multiout_composite(2); SELECT * FROM multiout_table_type_setof('dict', 'f', 3); +SELECT * FROM multiout_table_type_setof('dict', 'f', 7); SELECT * FROM multiout_table_type_setof('tuple', 'f', 2); +SELECT * FROM multiout_table_type_setof('tuple', 'f', 3); +SELECT * FROM multiout_table_type_setof('list', 'f', 2); +SELECT * FROM multiout_table_type_setof('list', 'f', 3); SELECT * FROM multiout_table_type_setof('obj', 'f', 4); +SELECT * FROM multiout_table_type_setof('obj', 'f', 5); +SELECT * FROM multiout_table_type_setof('str', 'f', 6); +SELECT * FROM multiout_table_type_setof('str', 'f', 7); SELECT * FROM multiout_table_type_setof('dict', 't', 3); -- check what happens if a type changes under us