8000 Make json{b}_populate_recordset() use the right tuple descriptor. · koderP/postgres@c0c8807 · GitHub
[go: up one dir, main page]

Skip to content

Commit c0c8807

Browse files
committed
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
1 parent b500297 commit c0c8807

File tree

3 files changed

+55
-21
lines changed

3 files changed

+55
-21
lines changed

src/backend/utils/adt/jsonfuncs.c

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,19 +1601,47 @@ json_populate_recordset(PG_FUNCTION_ARGS)
16011601
errmsg("set-valued function called in context that "
16021602
"cannot accept a set")));
16031603

1604-
16051604
rsi->returnMode = SFRM_Materialize;
16061605

1607-
/*
1608-
* get the tupdesc from the result set info - it must be a record type
1609-
* because we already checked that arg1 is a record type.
1610-
*/
1611-
(void) get_call_result_type(fcinfo, NULL, &tupdesc);
1606+
/* if the json is null send back an empty set */
1607+
if (PG_ARGISNULL(1))
1608+
PG_RETURN_NULL();
1609+
1610+
json = PG_GETARG_TEXT_P(1);
1611+
1612+
if (PG_ARGISNULL(0))
1613+
{
1614+
rec = NULL;
1615+
1616+
/*
1617+
* get the tupdesc from the result set info - it must be a record type
1618+
* because we already checked that arg1 is a record type
1619+
*/
1620+
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
1621+
ereport(ERROR,
1622+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1623+
errmsg("function returning record called in context "
1624+
"that cannot accept type record")));
1625+
}
1626+
else
1627+
{
1628+
rec = PG_GETARG_HEAPTUPLEHEADER(0);
1629+
1630+
/*
1631+
* use the input record's own type marking to find a tupdesc for it.
1632+
*/
1633+
tupType = HeapTupleHeaderGetTypeId(rec);
1634+
tupTypmod = HeapTupleHeaderGetTypMod(rec);
1635+
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
1636+
}
1637+
1638+
tupType = tupdesc->tdtypeid;
1639+
tupTypmod = tupdesc->tdtypmod;
1640+
ncolumns = tupdesc->natts;
16121641

16131642
state = palloc0(sizeof(PopulateRecordsetState));
16141643
sem = palloc0(sizeof(JsonSemAction));
16151644

1616-
16171645
/* make these in a sufficiently long-lived memory context */
16181646
old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
16191647

@@ -1625,20 +1653,8 @@ json_populate_recordset(PG_FUNCTION_ARGS)
16251653

16261654
MemoryContextSwitchTo(old_cxt);
16271655

1628-
/* if the json is null send back an empty set */
1629-
if (PG_ARGISNULL(1))
1630-
PG_RETURN_NULL();
1631-
1632-
json = PG_GETARG_TEXT_P(1);
1633-
1634-
if (PG_ARGISNULL(0))
1635-
rec = NULL;
1636-
else
1637-
rec = PG_GETARG_HEAPTUPLEHEADER(0);
1638-
1639-
tupType = tupdesc->tdtypeid;
1640-
tupTypmod = tupdesc->tdtypmod;
1641-
ncolumns = tupdesc->natts;
1656+
/* unnecessary, but harmless, if tupdesc came from get_call_result_type: */
1657+
ReleaseTupleDesc(tupdesc);
16421658

16431659
lex = makeJsonLexContext(json, true);
16441660

src/test/regress/expected/json.out

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,3 +907,16 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":[100,200,3
907907
ERROR: cannot call json_populate_recordset on a nested object
908908
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;
909909
ERROR: cannot call json_populate_recordset on a nested object
910+
-- negative cases where the wrong record type is supplied
911+
select * from json_populate_recordset(row(0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
912+
ERROR: function return row and query-specified return row do not match
913+
DETAIL: Returned row contains 1 attribute, but query expects 2.
914+
select * from json_populate_recordset(row(0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
915+
ERROR: function return row and query-specified return row do not match
916+
DETAIL: Returned type integer at ordinal position 1, but query expects text.
917+
select * from json_populate_recordset(row(0::int,0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
918+
ERROR: function return row and query-specified return row do not match
919+
DETAIL: Returned row contains 3 attributes, but query expects 2.
920+
select * from json_populate_recordset(row(1000000000::int,50::int),'[{"b":"2"},{"a":"3"}]') q (a text, b text);
921+
ERROR: function return row and query-specified return row do not match
922+
DETAIL: Returned type integer at ordinal position 1, but query expects text.

src/test/regress/sql/json.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,8 @@ select * from json_populate_recordset(row('def',99,null)::jpop,'[{"a":"blurfl","
302302
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;
303303
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;
304304

305+
-- negative cases where the wrong record type is supplied
306+
select * from json_populate_recordset(row(0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
307+
select * from json_populate_recordset(row(0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
308+
select * from json_populate_recordset(row(0::int,0::int,0::int),'[{"a":"1","b":"2"},{"a":"3"}]') q (a text, b text);
309+
select * from json_populate_recordset(row(1000000000::int,50::int),'[{"b":"2"},{"a":"3"}]') q (a text, b text);

0 commit comments

Comments
 (0)
0