diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2022-12-12 16:17:49 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2022-12-12 16:17:54 -0500 |
commit | b0feda79fdf02710a6039a324299525845a4a9d4 (patch) | |
tree | 488eb42fc75b6554156f6bd359d8e15a9fd37c32 /src/backend/utils/adt/jsonfuncs.c | |
parent | 101c37cd342a3ae134bb3e5e0abb14ae46692b56 (diff) | |
download | postgresql-b0feda79fdf02710a6039a324299525845a4a9d4.tar.gz postgresql-b0feda79fdf02710a6039a324299525845a4a9d4.zip |
Fix jsonb subscripting to cope with toasted subscript values.
jsonb_get_element() was incautious enough to use VARDATA() and
VARSIZE() directly on an arbitrary text Datum. That of course
fails if the Datum is short-header, compressed, or out-of-line.
The typical result would be failing to match any element of a
jsonb object, though matching the wrong one seems possible as well.
setPathObject() was slightly brighter, in that it used VARDATA_ANY
and VARSIZE_ANY_EXHDR, but that only kept it out of trouble for
short-header Datums. push_path() had the same issue. This could
result in faulty subscripted insertions, though keys long enough to
cause a problem are likely rare in the wild.
Having seen these, I looked around for unsafe usages in the rest
of the adt/json* files. There are a couple of places where it's not
immediately obvious that the Datum can't be compressed or out-of-line,
so I added pg_detoast_datum_packed() to cope if it is. Also, remove
some other usages of VARDATA/VARSIZE on Datums we just extracted from
a text array. Those aren't actively broken, but they will become so
if we ever start allowing short-header array elements, which does not
seem like a terribly unreasonable thing to do. In any case they are
not great coding examples, and they could also do with comments
pointing out that we're assuming we don't need pg_detoast_datum_packed.
Per report from exe-dealer@yandex.ru. Patch by me, but thanks to
David Johnston for initial investigation. Back-patch to v14 where
jsonb subscripting was introduced.
Discussion: https://postgr.es/m/205321670615953@mail.yandex.ru
Diffstat (limited to 'src/backend/utils/adt/jsonfuncs.c')
-rw-r--r-- | src/backend/utils/adt/jsonfuncs.c | 41 |
1 files changed, 29 insertions, 12 deletions
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 376a9b7ab82..2d293da18ef 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -527,6 +527,12 @@ pg_parse_json_or_errsave(JsonLexContext *lex, JsonSemAction *sem, JsonLexContext * makeJsonLexContext(text *json, bool need_escapes) { + /* + * Most callers pass a detoasted datum, but it's not clear that they all + * do. pg_detoast_datum_packed() is cheap insurance. + */ + json = pg_detoast_datum_packed(json); + return makeJsonLexContextCstringLen(VARDATA_ANY(json), VARSIZE_ANY_EXHDR(json), GetDatabaseEncoding(), @@ -1559,9 +1565,11 @@ jsonb_get_element(Jsonb *jb, Datum *path, int npath, bool *isnull, bool as_text) { if (have_object) { + text *subscr = DatumGetTextPP(path[i]); + jbvp = getKeyJsonValueFromContainer(container, - VARDATA(path[i]), - VARSIZE(path[i]) - VARHDRSZ, + VARDATA_ANY(subscr), + VARSIZE_ANY_EXHDR(subscr), NULL); } else if (have_array) @@ -1734,8 +1742,8 @@ push_path(JsonbParseState **st, int level, Datum *path_elems, { /* text, an object is expected */ newkey.type = jbvString; - newkey.val.string.len = VARSIZE_ANY_EXHDR(path_elems[i]); - newkey.val.string.val = VARDATA_ANY(path_elems[i]); + newkey.val.string.val = c; + newkey.val.string.len = strlen(c); (void) pushJsonbValue(st, WJB_BEGIN_OBJECT, NULL); (void) pushJsonbValue(st, WJB_KEY, &newkey); @@ -4456,6 +4464,7 @@ jsonb_delete_array(PG_FUNCTION_ARGS) if (keys_nulls[i]) continue; + /* We rely on the array elements not being toasted */ keyptr = VARDATA_ANY(keys_elems[i]); keylen = VARSIZE_ANY_EXHDR(keys_elems[i]); if (keylen == v.val.string.len && @@ -4977,6 +4986,7 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, int path_len, JsonbParseState **st, int level, JsonbValue *newval, uint32 npairs, int op_type) { + text *pathelem = NULL; int i; JsonbValue k, v; @@ -4984,6 +4994,11 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, if (level >= path_len || path_nulls[level]) done = true; + else + { + /* The path Datum could be toasted, in which case we must detoast it */ + pathelem = DatumGetTextPP(path_elems[level]); + } /* empty object is a special case for create */ if ((npairs == 0) && (op_type & JB_PATH_CREATE_OR_INSERT) && @@ -4992,8 +5007,8 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, JsonbValue newkey; newkey.type = jbvString; - newkey.val.string.len = VARSIZE_ANY_EXHDR(path_elems[level]); - newkey.val.string.val = VARDATA_ANY(path_elems[level]); + newkey.val.string.val = VARDATA_ANY(pathelem); + newkey.val.string.len = VARSIZE_ANY_EXHDR(pathelem); (void) pushJsonbValue(st, WJB_KEY, &newkey); (void) pushJsonbValue(st, WJB_VALUE, newval); @@ -5006,8 +5021,8 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, Assert(r == WJB_KEY); if (!done && - k.val.string.len == VARSIZE_ANY_EXHDR(path_elems[level]) && - memcmp(k.val.string.val, VARDATA_ANY(path_elems[level]), + k.val.string.len == VARSIZE_ANY_EXHDR(pathelem) && + memcmp(k.val.string.val, VARDATA_ANY(pathelem), k.val.string.len) == 0) { done = true; @@ -5047,8 +5062,8 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, JsonbValue newkey; newkey.type = jbvString; - newkey.val.string.len = VARSIZE_ANY_EXHDR(path_elems[level]); - newkey.val.string.val = VARDATA_ANY(path_elems[level]); + newkey.val.string.val = VARDATA_ANY(pathelem); + newkey.val.string.len = VARSIZE_ANY_EXHDR(pathelem); (void) pushJsonbValue(st, WJB_KEY, &newkey); (void) pushJsonbValue(st, WJB_VALUE, newval); @@ -5091,8 +5106,8 @@ setPathObject(JsonbIterator **it, Datum *path_elems, bool *path_nulls, JsonbValue newkey; newkey.type = jbvString; - newkey.val.string.len = VARSIZE_ANY_EXHDR(path_elems[level]); - newkey.val.string.val = VARDATA_ANY(path_elems[level]); + newkey.val.string.val = VARDATA_ANY(pathelem); + newkey.val.string.len = VARSIZE_ANY_EXHDR(pathelem); (void) pushJsonbValue(st, WJB_KEY, &newkey); (void) push_path(st, level, path_elems, path_nulls, @@ -5505,6 +5520,8 @@ transform_jsonb_string_values(Jsonb *jsonb, void *action_state, if ((type == WJB_VALUE || type == WJB_ELEM) && v.type == jbvString) { out = transform_action(action_state, v.val.string.val, v.val.string.len); + /* out is probably not toasted, but let's be sure */ + out = pg_detoast_datum_packed(out); v.val.string.val = VARDATA_ANY(out); v.val.string.len = VARSIZE_ANY_EXHDR(out); res = pushJsonbValue(&st, type, type < WJB_BEGIN_ARRAY ? &v : NULL); |