From c0c8807ded2f59c25b375998ef24ff09994563a1 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 6 Nov 2017 10:29:17 -0500 Subject: [PATCH] Make json{b}_populate_recordset() use the right tuple descriptor. 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 | 58 +++++++++++++++++++----------- src/test/regress/expected/json.out | 13 +++++++ src/test/regress/sql/json.sql | 5 +++ 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 6d1fed7c063..3258db9449f 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -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); diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index bdedf3a7010..6df9af29f97 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -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. diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 7a8c7b138a5..c6a07ef88be 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -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);