Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8422656

Browse files
committed
Fix unique key checks in JSON object constructors
When building a JSON object, the code builds a hash table of keys, to allow checking if the keys are unique. The uniqueness check and adding the new key happens in json_unique_check_key(), but this assumes the pointer to the key remains valid. Unfortunately, two places passed pointers to keys in a buffer, while also appending more data (additional key/value pairs) to the buffer. With enough data the buffer is resized by enlargeStringInfo(), which calls repalloc(), invalidating the earlier key pointers. Due to this the uniqueness check may fail with both false negatives and false positives, producing JSON objects with duplicate keys or failing to produce a perfectly valid JSON object. This affects multiple functions that enforce uniqueness of keys, all introduced in PG16 with the new SQL/JSON: - json_object_agg_unique / jsonb_object_agg_unique - json_object / jsonb_objectagg Existing regression tests did not detect the issue, simply because the initial buffer size is 1024 and the objects were small enough not to require the repalloc. With a sufficiently large object, AddressSanitizer reported the access to invalid memory immediately. So would valgrind, of course. Fixed by copying the key into the hash table memory context, and adding regression tests with enough data to repalloc the buffer. Backpatch to 16, where the functions were introduced. Reported by Alexander Lakhin. Investigation and initial fix by Junwang Zhao, with various improvements and tests by me. Reported-by: Alexander Lakhin Author: Junwang Zhao, Tomas Vondra Backpatch-through: 16 Discussion: https://postgr.es/m/18598-3279ed972a2347c7@postgresql.org Discussion: https://postgr.es/m/CAEG8a3JjH0ReJF2_O7-8LuEbO69BxPhYeXs95_x7+H9AMWF1gw@mail.gmail.com
1 parent 6b25c57 commit 8422656

File tree

5 files changed

+32
-3
lines changed

5 files changed

+32
-3
lines changed

src/backend/utils/adt/json.c

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,14 @@ json_object_agg_transfn_worker(FunctionCallInfo fcinfo,
11111111

11121112
if (unique_keys)
11131113
{
1114-
const char *key = &out->data[key_offset];
1114+
/*
1115+
* Copy the key first, instead of pointing into the buffer. It will be
1116+
* added to the hash table, but the buffer may get reallocated as
1117+
* we're appending more data to it. That would invalidate pointers to
1118+
* keys in the current buffer.
1119+
*/
1120+
const char *key = MemoryContextStrdup(aggcontext,
1121+
&out->data[key_offset]);
11151122

11161123
if (!json_unique_check_key(&state->unique_check.check, key, 0))
11171124
ereport(ERROR,
@@ -1274,8 +1281,15 @@ json_build_object_worker(int nargs, const Datum *args, const bool *nulls, const
12741281

12751282
if (unique_keys)
12761283
{
1277-
/* check key uniqueness after key appending */
1278-
const char *key = &out->data[key_offset];
1284+
/*
1285+
* check key uniqueness after key appending
1286+
*
1287+
* Copy the key first, instead of pointing into the buffer. It
1288+
* will be added to the hash table, but the buffer may get
1289+
* reallocated as we're appending more data to it. That would
1290+
* invalidate pointers to keys in the current buffer.
1291+
*/
1292+
const char *key = pstrdup(&out->data[key_offset]);
12791293

12801294
if (!json_unique_check_key(&unique_check.check, key, 0))
12811295
ereport(ERROR,

src/test/regress/expected/json.out

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,6 +2330,9 @@ select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
23302330
{"a" : "1", "b" : "2", "" : "3", "d e f" : "a b c"}
23312331
(1 row)
23322332

2333+
-- json_object_agg_unique requires unique keys
2334+
select json_object_agg_unique(mod(i,100), i) from generate_series(0, 199) i;
2335+
ERROR: duplicate JSON object key value: "0"
23332336
-- json_to_record and json_to_recordset
23342337
select * from json_to_record('{"a":1,"b":"foo","c":"bar"}')
23352338
as x(a int, b text, d text);

src/test/regress/expected/sqljson.out

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,8 @@ SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL);
537537
{"a" : "1", "c" : 2}
538538
(1 row)
539539

540+
SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, repeat('x', 1000): 1, 2: repeat('a', 100) WITH UNIQUE);
541+
ERROR: duplicate JSON object key value: "2"
540542
SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE);
541543
ERROR: duplicate JSON object key value: "1"
542544
SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE);
@@ -921,6 +923,9 @@ FROM (VALUES (1, 1), (0, NULL),(4, null), (5, null),(6, null),(2, 2)) foo(k, v);
921923
{"1": 1, "2": 2}
922924
(1 row)
923925

926+
SELECT JSON_OBJECTAGG(mod(i,100): (i)::text FORMAT JSON WITH UNIQUE)
927+
FROM generate_series(0, 199) i;
928+
ERROR: duplicate JSON object key value: "0"
924929
-- Test JSON_OBJECT deparsing
925930
EXPLAIN (VERBOSE, COSTS OFF)
926931
SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);

src/test/regress/sql/json.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,8 @@ select json_object('{a,b,NULL,"d e f"}','{1,2,3,"a b c"}');
755755

756756
select json_object('{a,b,"","d e f"}','{1,2,3,"a b c"}');
757757

758+
-- json_object_agg_unique requires unique keys
759+
select json_object_agg_unique(mod(i,100), i) from generate_series(0, 199) i;
758760

759761
-- json_to_record and json_to_recordset
760762

src/test/regress/sql/sqljson.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2);
138138
SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 NULL ON NULL);
139139
SELECT JSON_OBJECT('a': '1', 'b': NULL, 'c': 2 ABSENT ON NULL);
140140

141+
SELECT JSON_OBJECT(1: 1, '2': NULL, '3': 1, repeat('x', 1000): 1, 2: repeat('a', 100) WITH UNIQUE);
142+
141143
SELECT JSON_OBJECT(1: 1, '1': NULL WITH UNIQUE);
142144
SELECT JSON_OBJECT(1: 1, '1': NULL ABSENT ON NULL WITH UNIQUE);
143145
SELECT JSON_OBJECT(1: 1, '1': NULL NULL ON NULL WITH UNIQUE RETURNING jsonb);
@@ -283,6 +285,9 @@ FROM (VALUES (1, 1), (1, NULL), (2, 2)) foo(k, v);
283285
SELECT JSON_OBJECTAGG(k: v ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb)
284286
FROM (VALUES (1, 1), (0, NULL),(4, null), (5, null),(6, null),(2, 2)) foo(k, v);
285287

288+
SELECT JSON_OBJECTAGG(mod(i,100): (i)::text FORMAT JSON WITH UNIQUE)
289+
FROM generate_series(0, 199) i;
290+
286291
-- Test JSON_OBJECT deparsing
287292
EXPLAIN (VERBOSE, COSTS OFF)
288293
SELECT JSON_OBJECT('foo' : '1' FORMAT JSON, 'bar' : 'baz' RETURNING json);

0 commit comments

Comments
 (0)