diff options
author | Tomas Vondra <tomas.vondra@postgresql.org> | 2024-09-11 13:21:05 +0200 |
---|---|---|
committer | Tomas Vondra <tomas.vondra@postgresql.org> | 2024-09-11 13:21:10 +0200 |
commit | 842265631dfd9e5583d934e2ae328b09bc4b2f8b (patch) | |
tree | 4e50ecf7c9f29799c26289644d554297edc347df /src/backend/utils/adt/json.c | |
parent | 6b25c57a2dd61a44bdb60d5424d4aaa7e0f14334 (diff) | |
download | postgresql-842265631dfd9e5583d934e2ae328b09bc4b2f8b.tar.gz postgresql-842265631dfd9e5583d934e2ae328b09bc4b2f8b.zip |
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
Diffstat (limited to 'src/backend/utils/adt/json.c')
-rw-r--r-- | src/backend/utils/adt/json.c | 20 |
1 files changed, 17 insertions, 3 deletions
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index 4eeeeaf0a60..058aade2af4 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -1111,7 +1111,14 @@ json_object_agg_transfn_worker(FunctionCallInfo fcinfo, if (unique_keys) { - const char *key = &out->data[key_offset]; + /* + * Copy the key first, instead of pointing into the buffer. It will be + * added to the hash table, but the buffer may get reallocated as + * we're appending more data to it. That would invalidate pointers to + * keys in the current buffer. + */ + const char *key = MemoryContextStrdup(aggcontext, + &out->data[key_offset]); if (!json_unique_check_key(&state->unique_check.check, key, 0)) ereport(ERROR, @@ -1274,8 +1281,15 @@ json_build_object_worker(int nargs, const Datum *args, const bool *nulls, const if (unique_keys) { - /* check key uniqueness after key appending */ - const char *key = &out->data[key_offset]; + /* + * check key uniqueness after key appending + * + * Copy the key first, instead of pointing into the buffer. It + * will be added to the hash table, but the buffer may get + * reallocated as we're appending more data to it. That would + * invalidate pointers to keys in the current buffer. + */ + const char *key = pstrdup(&out->data[key_offset]); if (!json_unique_check_key(&unique_check.check, key, 0)) ereport(ERROR, |