Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Make json{b}_populate_recordset() use the right tuple descriptor.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Nov 2017 15:29:17 +0000 (10:29 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Nov 2017 15:29:42 +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/sql/json.sql

index 6d1fed7c063b16b9583d5e5ad61a83809974dbc7..3258db9449fc9043bee14316d9faa4f8f25da499 100644 (file)
@@ -1601,19 +1601,47 @@ json_populate_recordset(PG_FUNCTION_ARGS)
                 errmsg("set-valued function called in context that "
                        "cannot accept a set")));
 
-
    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.
-    */
-   (void) get_call_result_type(fcinfo, NULL, &tupdesc);
+   /* if the json is null send back an empty set */
+   if (PG_ARGISNULL(1))
+       PG_RETURN_NULL();
+
+   json = PG_GETARG_TEXT_P(1);
+
+   if (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
+        */
+       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;
 
    state = palloc0(sizeof(PopulateRecordsetState));
    sem = palloc0(sizeof(JsonSemAction));
 
-
    /* make these in a sufficiently long-lived memory context */
    old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
 
@@ -1625,20 +1653,8 @@ json_populate_recordset(PG_FUNCTION_ARGS)
 
    MemoryContextSwitchTo(old_cxt);
 
-   /* if the json is null send back an empty set */
-   if (PG_ARGISNULL(1))
-       PG_RETURN_NULL();
-
-   json = PG_GETARG_TEXT_P(1);
-
-   if (PG_ARGISNULL(0))
-       rec = NULL;
-   else
-       rec = PG_GETARG_HEAPTUPLEHEADER(0);
-
-   tupType = tupdesc->tdtypeid;
-   tupTypmod = tupdesc->tdtypmod;
-   ncolumns = tupdesc->natts;
+   /* unnecessary, but harmless, if tupdesc came from get_call_result_type: */
+   ReleaseTupleDesc(tupdesc);
 
    lex = makeJsonLexContext(json, true);
 
index bdedf3a701067a58aba0362560d724002c861278..6df9af29f974ffabab7726cee7ed8cb092055db3 100644 (file)
@@ -907,3 +907,16 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
 ERROR:  cannot call json_populate_recordset on a nested object
 select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[100,200,300],"x":43.2},{"a":{"z":true},"b":3,"c":"2012-01-20 10:42:53"}]') q;
 ERROR:  cannot call json_populate_recordset on a nested object
+-- 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.
index 7a8c7b138a5bca6736e8089a2485cdba7092fc8b..c6a07ef88beb15e26276cd33ed3a66d2faffadd8 100644 (file)
@@ -302,3 +302,8 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","
 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;
 select * from json_populate_recordset(row('def',99,null)::jpop,'[{"c":[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);