From 6a6ae402ab390173dcd1ad3221f07c37c3260a96 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Tue, 21 Jul 2026 18:32:41 +1000 Subject: [PATCH] MDEV-32331: JSON path functions with no charset on path crash server Across a range of JSON functions taking a path argument there are SQL expressions that dont' have a character set. If these expressions don't have a character set fall back to the character set of the argument of the json function that represent the document being operated on. If this doesn't have a character set fall back to my_charset_utf8mb4_bin. This covers the 11.4 JSON_KEY_VALUE function also as it reuses the Json_path_extractor::extract method. Add nonnull and warn_unused_result to the json path functions to facilitate compiler and UBSAN catching of the problem early. As null values of s_p are incompatible with report_path_error, jump directly to a null return which is consistent with the defination of the JSON sql funciton. --- include/json_lib.h | 1 + mysql-test/main/func_json.result | 63 +++++++++++++++++++++++++++ mysql-test/main/func_json.test | 20 +++++++++ sql/item_jsonfunc.cc | 74 +++++++++++++++++++++++--------- 4 files changed, 137 insertions(+), 21 deletions(-) diff --git a/include/json_lib.h b/include/json_lib.h index e12ab10b4d10c..d71fcd243be76 100644 --- a/include/json_lib.h +++ b/include/json_lib.h @@ -114,6 +114,7 @@ typedef struct st_json_path_t } json_path_t; +__attribute__((nonnull, warn_unused_result)) int json_path_setup(json_path_t *p, CHARSET_INFO *i_cs, const uchar *str, const uchar *end); diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index e2141af890845..65883d3f769f7 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -2938,4 +2938,67 @@ JSON_EXTRACT('0E+0','$') SELECT JSON_ARRAY_INSERT (0,NULL,1) as j; j NULL +# +# MDEV-32331 JSON path functions with no charset on path crash server +# +SELECT 1 FROM +(SELECT CASE WHEN x * x THEN x END as a +FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 +) dt +WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_insert ( '[1]' , ( 'x' % 'x' ) , 1 , '$[0]' , 9 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_insert ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_remove ( '[1,2]' , ( 'x' % 'x' ) , '$[0]' ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_replace ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_set ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +1 +Warnings: +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +Warning 1292 Truncated incorrect DOUBLE value: 'x' +SELECT x FROM (SELECT 1 AS x UNION SELECT 2) AS t WHERE x IN ( SELECT JSON_REPLACE('1', UPPER(CAST(NULL AS CHAR)), 100)); +x +SELECT ( WITH RECURSIVE x ( x ) AS (WITH RECURSIVE x ( x ) AS ( SELECT 1 UNION SELECT x + 1 FROM x ) SELECT json_array_append ( '[[], [], []]' , NOT ( NULL LIKE 'ABC%' ) , 315 )) SELECT x FROM x WHERE x > 10 AND x < 1) AS x; +x +NULL +SELECT 1 FROM (SELECT JSON_REMOVE(46, DATE(NULL)) AS x EXCEPT SELECT 1) AS d WHERE NOT(NOT(x > 10)) AND (x < 1 OR x > 1); +1 # End of 10.11 Test diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index e3d98d7525702..05cfb0c42c0c5 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -2114,4 +2114,24 @@ SELECT JSON_EXTRACT('0E+0','$'); SELECT JSON_ARRAY_INSERT (0,NULL,1) as j; +--echo # +--echo # MDEV-32331 JSON path functions with no charset on path crash server +--echo # + +SELECT 1 FROM + (SELECT CASE WHEN x * x THEN x END as a + FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 + ) dt + WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_append ( 'x' , ( 'x' % 'x' ) , 1 , 'x' , 1 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_array_insert ( '[1]' , ( 'x' % 'x' ) , 1 , '$[0]' , 9 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_insert ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_remove ( '[1,2]' , ( 'x' % 'x' ) , '$[0]' ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_replace ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; +SELECT 1 FROM (SELECT CASE WHEN x * x THEN x END as a FROM ( SELECT json_set ( '1' , ( 'x' % 'x' ) , 1 , '$.a' , 2 ) as x ) dt2 ) dt WHERE a; + +SELECT x FROM (SELECT 1 AS x UNION SELECT 2) AS t WHERE x IN ( SELECT JSON_REPLACE('1', UPPER(CAST(NULL AS CHAR)), 100)); +SELECT ( WITH RECURSIVE x ( x ) AS (WITH RECURSIVE x ( x ) AS ( SELECT 1 UNION SELECT x + 1 FROM x ) SELECT json_array_append ( '[[], [], []]' , NOT ( NULL LIKE 'ABC%' ) , 315 )) SELECT x FROM x WHERE x > 10 AND x < 1) AS x; +SELECT 1 FROM (SELECT JSON_REMOVE(46, DATE(NULL)) AS x EXCEPT SELECT 1) AS d WHERE NOT(NOT(x > 10)) AND (x < 1 OR x > 1); + --echo # End of 10.11 Test diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 1fa09a6c5f040..94805380cf566 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -572,6 +572,7 @@ void report_path_error_ex(const char *ps, json_path_t *p, Checks if the path has '.*' '[*]' or '**' constructions and sets the NO_WILDCARD_ALLOWED error if the case. */ +__attribute__((nonnull, warn_unused_result)) static int path_setup_nwc(json_path_t *p, CHARSET_INFO *i_cs, const uchar *str, const uchar *end) { @@ -586,6 +587,13 @@ static int path_setup_nwc(json_path_t *p, CHARSET_INFO *i_cs, return 1; } +static inline +CHARSET_INFO *def_path_charset(CHARSET_INFO *cs, CHARSET_INFO *alt) +{ + if (cs) return cs; + if (alt) return alt; + return &my_charset_utf8mb4_bin; +} bool Item_func_json_valid::val_bool() { @@ -768,18 +776,20 @@ bool Json_path_extractor::extract(String *str, Item *item_js, Item *item_jp, { String *s_p= item_jp->val_str(&tmp_path); + if (!s_p) + return true; if (allow_wildcard) { - if (s_p && + if (!s_p->charset() || json_path_setup(&p, s_p->charset(), (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) error= true; } else { - if (s_p && - path_setup_nwc(&p, s_p->charset(), (const uchar *) s_p->ptr(), - (const uchar *) s_p->ptr() + s_p->length())) + if (path_setup_nwc(&p, def_path_charset(s_p->charset(), cs), + (const uchar *) s_p->ptr(), + (const uchar *) s_p->ptr() + s_p->length())) error= true; } @@ -1599,8 +1609,11 @@ bool Item_func_json_contains::val_bool() if (!path.parsed) { String *s_p= args[2]->val_str(&tmp_path); - if (s_p && - path_setup_nwc(&path.p,s_p->charset(),(const uchar *) s_p->ptr(), + if (!s_p) + goto return_null; + if (path_setup_nwc(&path.p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->end())) { report_path_error(s_p, &path.p, 2); @@ -2160,8 +2173,11 @@ String *Item_func_json_array_append::val_str(String *str) if (!c_path->parsed) { String *s_p= args[n_arg]->val_str(tmp_paths+n_path); - if (s_p && - path_setup_nwc(&c_path->p,s_p->charset(),(const uchar *) s_p->ptr(), + if (!s_p) + goto return_null; + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { report_path_error(s_p, &c_path->p, n_arg); @@ -2297,15 +2313,19 @@ String *Item_func_json_array_insert::val_str(String *str) String *s_p= args[n_arg]->val_str(tmp_paths+n_path); if (!s_p) goto return_null; + if (!s_p->charset()) + goto path_err; - if (path_setup_nwc(&c_path->p,s_p->charset(),(const uchar *) s_p->ptr(), - (const uchar *) s_p->ptr() + s_p->length()) || + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), + (const uchar *) s_p->ptr() + s_p->length()) || c_path->p.last_step - 1 < c_path->p.steps || c_path->p.last_step->type != JSON_PATH_ARRAY) { if (c_path->p.s.error == 0) c_path->p.s.error= SHOULD_END_WITH_ARRAY; - +path_err: report_path_error(s_p, &c_path->p, n_arg); goto return_null; @@ -3161,8 +3181,11 @@ longlong Item_func_json_length::val_int() if (!path.parsed) { String *s_p= args[1]->val_str(&tmp_path); - if (s_p && - path_setup_nwc(&path.p, s_p->charset(), (const uchar *) s_p->ptr(), + if (!s_p) + goto null_return; + if (path_setup_nwc(&path.p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { report_path_error(s_p, &path.p, 1); @@ -3406,7 +3429,8 @@ String *Item_func_json_insert::val_str(String *str) String *s_p= args[n_arg]->val_str(tmp_paths+n_path); if (s_p) { - if (path_setup_nwc(&c_path->p,s_p->charset(), + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { @@ -3417,6 +3441,8 @@ String *Item_func_json_insert::val_str(String *str) /* We search to the last step. */ c_path->p.last_step--; } + else + goto return_null; c_path->parsed= c_path->constant; } if (args[n_arg]->null_value) @@ -3671,7 +3697,8 @@ String *Item_func_json_remove::val_str(String *str) String *s_p= args[n_arg]->val_str(tmp_paths+n_path); if (s_p) { - if (path_setup_nwc(&c_path->p,s_p->charset(), + if (path_setup_nwc(&c_path->p, + def_path_charset(s_p->charset(), js->charset()), (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) { @@ -3688,6 +3715,8 @@ String *Item_func_json_remove::val_str(String *str) goto null_return; } } + else + goto null_return; c_path->parsed= c_path->constant; } if (args[n_arg]->null_value) @@ -3901,13 +3930,16 @@ String *Item_func_json_keys::val_str(String *str) if (!path.parsed) { String *s_p= args[1]->val_str(&tmp_path); - if (s_p && - path_setup_nwc(&path.p, s_p->charset(), (const uchar *) s_p->ptr(), + if (!s_p) + goto null_return; + if (path_setup_nwc(&path.p, + def_path_charset(s_p->charset(), js->charset()), + (const uchar *) s_p->ptr(), (const uchar *) s_p->ptr() + s_p->length())) - { - report_path_error(s_p, &path.p, 1); - goto null_return; - } + { + report_path_error(s_p, &path.p, 1); + goto null_return; + } path.parsed= path.constant; }