Skip to content

Commit e6b94ed

Browse files
committed
Bug#21547877: UPDATE/INSERT JSON COLUMN CRASHES IF EXPRESSION REFERS TO SELF
Problem: If a multi-column update statement fails when updating one of the columns in a row, it will go on and update the remaining columns in that row before it stops and reports an error. If the failure happens when updating a JSON column, and the JSON column is also referenced later in the update statement, new and more serious errors can happen when the update statement attempts to read the JSON column, as it may contain garbage at this point. The fix is twofold: 1) Field_json::val_str() currently returns NULL if an error happens. This is correct for val_str() functions in the Item class hierarchy, but not for val_str() functions in the Field class hierarchy. The val_str() functions in the Field classes instead return a pointer to an empty String object on error. Since callers don't expect it to return NULL, this caused a crash when a caller unconditionally dereferenced the returned pointer. The patch makes Field_json::val_str() return a pointer to an empty String on error to avoid such crashes. 2) Whereas #1 fixes the immediate crash, Field_json::val_str() may still read garbage when this situation occurs. This could lead to unreliable behaviour, and both valgrind and ASAN warn about it. The patch therefore also makes Field_json::store() start by clearing the field, so that it will hold an empty value rather than garbage after an error has happened. Fix #2 is sufficient to fix the reported problems. Fix #1 is included for consistency, so that Field_json::val_str() behaves the same way as the other Field::val_str() functions. The query in the bug report didn't always crash. Since the root cause was that it had read garbage, it could be lucky and read something that looked like a valid value. In that case, Field_json::val_str() didn't return NULL, and the crash was avoided. The patch also makes these changes: - It removes the Field_json::store_dom() function, since it is only called from one place. It is now inlined instead. - It corrects information about return values in the comment that describes the ensure_utf8mb4() function.
1 parent af84e30 commit e6b94ed

File tree

5 files changed

+40
-26
lines changed

5 files changed

+40
-26
lines changed

mysql-test/r/json.result

+13
Original file line numberDiff line numberDiff line change
@@ -13267,3 +13267,16 @@ select row(uuid(), a) < row(a, str_to_date(1,1)) from t;
1326713267
row(uuid(), a) < row(a, str_to_date(1,1))
1326813268
1
1326913269
drop table t;
13270+
# Bug#21547877: UPDATE/INSERT JSON COLUMN CRASHES IF EXPRESSION
13271+
# REFERS TO SELF
13272+
#
13273+
SET NAMES latin1;
13274+
CREATE TABLE t (j JSON);
13275+
INSERT INTO t VALUES ('{}');
13276+
UPDATE t SET j='1', j='1111-11-11', j=('1' NOT BETWEEN j AND '1');
13277+
ERROR 22032: Invalid JSON text: "The document root must not follow by other values." at position 4 in value (or column) '1111-11-11'.
13278+
SELECT * FROM t;
13279+
j
13280+
{}
13281+
DROP TABLE t;
13282+
SET NAMES DEFAULT;

mysql-test/t/json.test

+11
Original file line numberDiff line numberDiff line change
@@ -6043,6 +6043,17 @@ create table t(a json not null) engine=innodb;
60436043
insert into t values('{}');
60446044
select row(uuid(), a) < row(a, str_to_date(1,1)) from t;
60456045
drop table t;
6046+
--echo # Bug#21547877: UPDATE/INSERT JSON COLUMN CRASHES IF EXPRESSION
6047+
--echo # REFERS TO SELF
6048+
--echo #
6049+
SET NAMES latin1;
6050+
CREATE TABLE t (j JSON);
6051+
INSERT INTO t VALUES ('{}');
6052+
--error ER_INVALID_JSON_TEXT
6053+
UPDATE t SET j='1', j='1111-11-11', j=('1' NOT BETWEEN j AND '1');
6054+
SELECT * FROM t;
6055+
DROP TABLE t;
6056+
SET NAMES DEFAULT;
60466057

60476058
# Local Variables:
60486059
# mode: sql

sql/field.cc

+14-23
Original file line numberDiff line numberDiff line change
@@ -8717,7 +8717,7 @@ uint Field_json::is_equal(Create_field *new_field)
87178717
/**
87188718
Store data in this JSON field.
87198719
8720-
JSON data is usually stored using store_dom() or store_json(), so this
8720+
JSON data is usually stored using store(Field_json*) or store_json(), so this
87218721
function will only be called if non-JSON data is attempted stored in a JSON
87228722
field. This results in an error in most cases.
87238723
@@ -8746,6 +8746,14 @@ Field_json::store(const char *from, size_t length, const CHARSET_INFO *cs)
87468746
{
87478747
ASSERT_COLUMN_MARKED_FOR_WRITE;
87488748

8749+
/*
8750+
First clear the field so that it doesn't contain garbage if we
8751+
return with an error. Some callers continue for a while even after
8752+
an error has been raised, and they could get into trouble if the
8753+
field contains garbage.
8754+
*/
8755+
reset();
8756+
87498757
const char *s;
87508758
size_t ss;
87518759
String v(from, length, cs);
@@ -8770,22 +8778,7 @@ Field_json::store(const char *from, size_t length, const CHARSET_INFO *cs)
87708778
return TYPE_ERR_BAD_VALUE;
87718779
}
87728780

8773-
return store_dom(dom.get());
8774-
}
8775-
8776-
8777-
/**
8778-
Convert a Json_dom object to binary representation and store it in this
8779-
field.
8780-
8781-
@param dom the Json_dom to store
8782-
@return zero on success, non-zero on failure
8783-
*/
8784-
type_conversion_status Field_json::store_dom(const Json_dom *dom)
8785-
{
8786-
ASSERT_COLUMN_MARKED_FOR_WRITE;
8787-
8788-
if (json_binary::serialize(dom, &value))
8781+
if (json_binary::serialize(dom.get(), &value))
87898782
return TYPE_ERR_BAD_VALUE;
87908783

87918784
return store_binary(value.ptr(), value.length());
@@ -8986,19 +8979,17 @@ String *Field_json::val_str(String *buf1, String *buf2 __attribute__((unused)))
89868979
{
89878980
ASSERT_COLUMN_MARKED_FOR_READ;
89888981

8989-
Json_wrapper wr;
8990-
if (is_null() || val_json(&wr))
8991-
return NULL;
8992-
89938982
/*
89948983
Per contract of Field::val_str(String*,String*), buf1 should be
89958984
used if the value needs to be converted to string, and buf2 should
89968985
be used if the string value is already known. We need to convert,
89978986
so use buf1.
89988987
*/
89998988
buf1->length(0);
9000-
if (wr.to_string(buf1, true, field_name))
9001-
return NULL; /* purecov: inspected */
8989+
8990+
Json_wrapper wr;
8991+
if (is_null() || val_json(&wr) || wr.to_string(buf1, true, field_name))
8992+
buf1->length(0);
90028993

90038994
return buf1;
90048995
}

sql/field.h

-1
Original file line numberDiff line numberDiff line change
@@ -3907,7 +3907,6 @@ class Field_geom :public Field_blob {
39073907
class Field_json :public Field_blob
39083908
{
39093909
type_conversion_status unsupported_conversion();
3910-
type_conversion_status store_dom(const Json_dom *dom);
39113910
type_conversion_status store_binary(const char *ptr, size_t length);
39123911
public:
39133912
Field_json(uchar *ptr_arg, uchar *null_ptr_arg, uint null_bit_arg,

sql/item_json_func.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ bool get_json_atom_wrapper(Item **args, uint arg_idx,
222222
/**
223223
Check a non-empty val for character set. If it has character set
224224
my_charset_binary, signal error and return false. Else, try to convert to
225-
my_charset_utf8mb4_binary. If this fails, signal error and return false, else
226-
return true.
225+
my_charset_utf8mb4_binary. If this fails, signal error and return true, else
226+
return false.
227227
228228
@param[in] val the string to be checked
229229
@param[in,out] buf buffer to hold the converted string

0 commit comments

Comments
 (0)