Fix plpython crash when returning string representation of a RECORD result.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 21 Aug 2015 16:21:37 +0000 (12:21 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 21 Aug 2015 16:21:37 +0000 (12:21 -0400)
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.

src/pl/plpython/expected/plpython_composite.out
src/pl/plpython/plpy_typeio.c
src/pl/plpython/sql/plpython_composite.sql

index ad454f37394bfbd2f6147333a310d66a905093e9..0ef0c21235cd87f8915eb9bbb97d37936b595256 100644 (file)
@@ -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 
 ---+---------
index 7a45b22871459559b3c325df2c12ad7169271581..05add6e2ce88f8a402bde968ea4bcc36e2ddc975 100644 (file)
@@ -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;
 }
 
 
index cb5fffeba9d8a1132ece9c22cf4ed6b2a3b910dd..473342c2f40b842338d72f1b452c604bdeda4816 100644 (file)
@@ -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