From c08441ec42267b56838ade0d4d23236854a9ebed Mon Sep 17 00:00:00 2001 From: Luke Lu Date: Mon, 20 Jul 2026 19:52:07 +0000 Subject: [PATCH] MDEV-40385 use-of-uninitialized-value in Binary_string::c_ptr() SELECT KDF('','',1000,256) triggered an MSAN use-of-uninitialized-value report in Binary_string::c_ptr() (sql/sql_string.h) reached from Item_func_kdf::val_str(). The optional 4th argument (kdf_name) was evaluated into a result buffer and then read as a C string with c_ptr(). When that argument is an integer literal such as 256, Item_int::val_str() writes the digits "256" into the buffer without appending a trailing NUL, and String::alloc() intentionally skips reallocation, so the buffer stays non-"alloced" and unterminated. c_ptr() then reads Ptr[str_length] to test for an existing terminator, reading an uninitialized byte. The buffer is a stack-resident ValueBuffer from Protocol::send_result_set_row, so the byte is validly addressable but never initialized; only MSAN re-poisons the stack scope, which is why the report is MSAN-only and Valgrind does not flag it. Use c_ptr_safe() instead of c_ptr() when reading the kdf_name argument. c_ptr_safe() writes the NUL terminator after a capacity check without first reading Ptr[str_length], while c_ptr() reads that byte to detect an existing terminator. Behaviour is unchanged: a non-matching kdf_name still yields ER_STD_INVALID_ARGUMENT and NULL, and valid names (pbkdf2_hmac, hkdf) still work. The regression test main.mdev40385 reproduces the report only under an MSAN-instrumented build; on a normal build it passes on both the unfixed and fixed server because the stray read is harmless without a sanitizer. All new code of the whole pull request, including one or several files that are either new files or modified ones, are contributed under the BSD-new license. I am contributing on behalf of my employer Amazon Web Services, Inc. --- mysql-test/main/mdev40385.result | 35 ++++++++++++++++++++++++++++++++ mysql-test/main/mdev40385.test | 28 +++++++++++++++++++++++++ sql/item_strfunc.cc | 4 ++-- 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 mysql-test/main/mdev40385.result create mode 100644 mysql-test/main/mdev40385.test diff --git a/mysql-test/main/mdev40385.result b/mysql-test/main/mdev40385.result new file mode 100644 index 0000000000000..78f7b3b2239ea --- /dev/null +++ b/mysql-test/main/mdev40385.result @@ -0,0 +1,35 @@ +# +# MDEV-40385 use-of-uninitialized-value in Binary_string::c_ptr() +# +# KDF() with a 4th (kdf_name) argument that is an integer literal made +# Item_func_kdf::val_str() evaluate args[3]->val_str() into the shared +# result buffer and then call c_ptr() on it. The integer-to-string +# conversion does not NUL-terminate, and the buffer is not "alloced", +# so c_ptr() (sql/sql_string.h) read an uninitialized byte. +# Under MSAN this was flagged as use-of-uninitialized-value. +# +# The 4th argument that is not a valid kdf name must be rejected with a +# warning and NULL, without reading uninitialized memory. +# +SELECT KDF('','',1000,256); +KDF('','',1000,256) +NULL +Warnings: +Warning 3047 Invalid argument error: '256' in function kdf. +SELECT KDF('secret','salt',1000,999); +KDF('secret','salt',1000,999) +NULL +Warnings: +Warning 3047 Invalid argument error: '999' in function kdf. +# +# Valid kdf_name arguments must still work. +# +SELECT KDF('secret','salt',1000,'pbkdf2_hmac') IS NOT NULL; +KDF('secret','salt',1000,'pbkdf2_hmac') IS NOT NULL +1 +SELECT KDF('secret','salt') IS NOT NULL; +KDF('secret','salt') IS NOT NULL +1 +# +# End of 11.4 tests +# diff --git a/mysql-test/main/mdev40385.test b/mysql-test/main/mdev40385.test new file mode 100644 index 0000000000000..97830d54a340d --- /dev/null +++ b/mysql-test/main/mdev40385.test @@ -0,0 +1,28 @@ +# MSAN only: This test verifies the fix behaviorally (warning + NULL). The uninitialized +# read itself only triggers a failure when run on an MSAN build; on a normal +# build the test passes on both the unfixed and fixed server. +--echo # +--echo # MDEV-40385 use-of-uninitialized-value in Binary_string::c_ptr() +--echo # +--echo # KDF() with a 4th (kdf_name) argument that is an integer literal made +--echo # Item_func_kdf::val_str() evaluate args[3]->val_str() into the shared +--echo # result buffer and then call c_ptr() on it. The integer-to-string +--echo # conversion does not NUL-terminate, and the buffer is not "alloced", +--echo # so c_ptr() (sql/sql_string.h) read an uninitialized byte. +--echo # Under MSAN this was flagged as use-of-uninitialized-value. +--echo # +--echo # The 4th argument that is not a valid kdf name must be rejected with a +--echo # warning and NULL, without reading uninitialized memory. +--echo # +SELECT KDF('','',1000,256); +SELECT KDF('secret','salt',1000,999); + +--echo # +--echo # Valid kdf_name arguments must still work. +--echo # +SELECT KDF('secret','salt',1000,'pbkdf2_hmac') IS NOT NULL; +SELECT KDF('secret','salt') IS NOT NULL; + +--echo # +--echo # End of 11.4 tests +--echo # diff --git a/sql/item_strfunc.cc b/sql/item_strfunc.cc index c98dcdfeb03f1..27d4caabe5c5c 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -489,9 +489,9 @@ String *Item_func_kdf::val_str(String *buf) { if (String *s= args[3]->val_str(buf)) { - if (strcasecmp(s->c_ptr(), "hkdf") == 0) + if (strcasecmp(s->c_ptr_safe(), "hkdf") == 0) use_hkdf= true; - else if (strcasecmp(s->c_ptr(), "pbkdf2_hmac") != 0) + else if (strcasecmp(s->c_ptr_safe(), "pbkdf2_hmac") != 0) { invalid_argument_error(func_name(), ErrConvStringQ(s).ptr()); goto ret_null;