]> granicus.if.org Git - postgresql/commitdiff
Make json{b}_populate_recordset() use the right tuple descriptor.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Nov 2017 15:29:16 +0000 (10:29 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Nov 2017 15:29:39 +0000 (10:29 -0500)
json{b}_populate_recordset() used the tuple descriptor created from the
query-level AS clause without worrying about whether it matched the actual
input record type.  If it didn't, that would usually result in a crash,
though disclosure of server memory contents seems possible as well, for a
skilled attacker capable of issuing crafted SQL commands.  Instead, use
the query-supplied descriptor only when there is no input tuple to look at,
and otherwise get a tuple descriptor based on the input tuple's own type
marking.  The core code will detect any type mismatch in the latter case.

Michael Paquier and Tom Lane, per a report from David Rowley.
Back-patch to 9.3 where this functionality was introduced.

Security: CVE-2017-15098

src/backend/utils/adt/jsonfuncs.c
src/test/regress/expected/json.out
src/test/regress/expected/jsonb.out
src/test/regress/sql/json.sql
src/test/regress/sql/jsonb.sql

index 17ee4e40d470206ccb2e8b627a1eb7983a972f89..aab0fed90bf2d48fc40f973feeebc4ce2c019365 100644 (file)
@@ -2696,26 +2696,37 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
 
        rsi->returnMode = SFRM_Materialize;
 
-       /*
-        * get the tupdesc from the result set info - it must be a record type
-        * because we already checked that arg1 is a record type, or we're in a
-        * to_record function which returns a setof record.
-        */
-       if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-               ereport(ERROR,
-                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                errmsg("function returning record called in context "
-                                               "that cannot accept type record")));
-
        /* if the json is null send back an empty set */
        if (PG_ARGISNULL(json_arg_num))
                PG_RETURN_NULL();
 
        if (!have_record_arg || PG_ARGISNULL(0))
+       {
                rec = NULL;
+
+               /*
+                * get the tupdesc from the result set info - it must be a record type
+                * because we already checked that arg1 is a record type, or we're in
+                * a to_record function which returns a setof record.
+                */
+               if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("function returning record called in context "
+                                                       "that cannot accept type record")));
+       }
        else
+       {
                rec = PG_GETARG_HEAPTUPLEHEADER(0);
 
+               /*
+                * use the input record's own type marking to find a tupdesc for it.
+                */
+               tupType = HeapTupleHeaderGetTypeId(rec);
+               tupTypmod = HeapTupleHeaderGetTypMod(rec);
+               tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+       }
+
        tupType = tupdesc->tdtypeid;
        tupTypmod = tupdesc->tdtypmod;
        ncolumns = tupdesc->natts;
@@ -2759,6 +2770,9 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname,
                                                                                           false, work_mem);
        MemoryContextSwitchTo(old_cxt);
 
+       /* unnecessary, but harmless, if tupdesc came from get_call_result_type: */
+       ReleaseTupleDesc(tupdesc);
+
        state->function_name = funcname;
        state->my_extra = my_extra;
        state->rec = rec;
index 7217d4005c37edd357cc260c97b02b4030a986c9..3d5bd3b676364fd3a42f1e4989a7f4d0c01b718a 100644 (file)
@@ -1408,6 +1408,19 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
  {"z":true}    |  3 | Fri Jan 20 10:42:53 2012
 (2 rows)
 
+-- negative cases where the wrong record type is supplied
+select * from json_populate_recordset(row(0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+ERROR:  function return row and query-specified return row do not match
+DETAIL:  Returned row contains 1 attribute, but query expects 2.
+select * from json_populate_recordset(row(0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+ERROR:  function return row and query-specified return row do not match
+DETAIL:  Returned type integer at ordinal position 1, but query expects text.
+select * from json_populate_recordset(row(0::int,0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+ERROR:  function return row and query-specified return row do not match
+DETAIL:  Returned row contains 3 attributes, but query expects 2.
+select * from json_populate_recordset(row(1000000000::int,50::int),'[{"b":"2"},{"a":"3"}]') q (a text, b text);
+ERROR:  function return row and query-specified return row do not match
+DETAIL:  Returned type integer at ordinal position 1, but query expects text.
 --json_typeof() function
 select value, json_typeof(value)
   from (values (json '123.4'),
index 1d73db55af2b86b94c19a8ee5a9cc860a37106c9..d32a2dabb8c1fde4cd4ca0a2c9c2496e76903ff6 100644 (file)
@@ -2090,6 +2090,19 @@ SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200
  {"z": true}     |  3 | Fri Jan 20 10:42:53 2012
 (2 rows)
 
+-- negative cases where the wrong record type is supplied
+select * from jsonb_populate_recordset(row(0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+ERROR:  function return row and query-specified return row do not match
+DETAIL:  Returned row contains 1 attribute, but query expects 2.
+select * from jsonb_populate_recordset(row(0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+ERROR:  function return row and query-specified return row do not match
+DETAIL:  Returned type integer at ordinal position 1, but query expects text.
+select * from jsonb_populate_recordset(row(0::int,0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+ERROR:  function return row and query-specified return row do not match
+DETAIL:  Returned row contains 3 attributes, but query expects 2.
+select * from jsonb_populate_recordset(row(1000000000::int,50::int),'[{"b":"2"},{"a":"3"}]') q (a text, b text);
+ERROR:  function return row and query-specified return row do not match
+DETAIL:  Returned type integer at ordinal position 1, but query expects text.
 -- jsonb_to_record and jsonb_to_recordset
 select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}')
     as x(a int, b text, d text);
index 46919c500df9942c5f704ce6335786eb080cda0d..1f04f7f69793ab081373a4848bed9ba85570a43e 100644 (file)
@@ -404,6 +404,12 @@ select * from json_populate_recordset(null::jpop,'[{"a":"blurfl","x":43.2},{"b":
 select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
 select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
 
+-- negative cases where the wrong record type is supplied
+select * from json_populate_recordset(row(0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+select * from json_populate_recordset(row(0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+select * from json_populate_recordset(row(0::int,0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+select * from json_populate_recordset(row(1000000000::int,50::int),'[{"b":"2"},{"a":"3"}]') q (a text, b text);
+
 --json_typeof() function
 select value, json_typeof(value)
   from (values (json '123.4'),
index 68b35b6722de0e8d9c540d3fa2f6d9a7690dc37a..4c195b5527ab10f3880e63141bb3c30d46a37ec6 100644 (file)
@@ -520,6 +520,12 @@ SELECT * FROM jsonb_populate_recordset(NULL::jbpop,'[{"a":"blurfl","x":43.2},{"b
 SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":"blurfl","x":43.2},{"b":3,"c":"2012-01-20 10:42:53"}]') q;
 SELECT * FROM jsonb_populate_recordset(row('def',99,NULL)::jbpop,'[{"a":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
 
+-- negative cases where the wrong record type is supplied
+select * from jsonb_populate_recordset(row(0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+select * from jsonb_populate_recordset(row(0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+select * from jsonb_populate_recordset(row(0::int,0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
+select * from jsonb_populate_recordset(row(1000000000::int,50::int),'[{"b":"2"},{"a":"3"}]') q (a text, b text);
+
 -- jsonb_to_record and jsonb_to_recordset
 
 select * from jsonb_to_record('{"a":1,"b":"foo","c":"bar"}')