From 5fb6444a371a5d85c832e63c02e6e8eaec2769f7 Mon Sep 17 00:00:00 2001 From: Alexander Barkov Date: Tue, 30 Apr 2019 10:53:59 +0400 Subject: [PATCH] MDEV-18738 ASAN heap-use-after-free in copy_if_not_alloced / copy_fields copy_if_not_alloced() did not handle situations when "from" is a constant string pointing to a substring of "to", so this code part freed "to" but then tried to copy its old (already freed) content to a new buffer: if (to->realloc(from_length)) return from; if ((to->str_length=MY_MIN(from->str_length,from_length))) memcpy(to->Ptr,from->Ptr,to->str_length); Adding a new code piece that catches such constant substrings and propery reallocs "to" to preserve its important part referenced by "from". --- mysql-test/r/func_str.result | 27 +++++++++++++++++++++++++++ mysql-test/t/func_str.test | 26 ++++++++++++++++++++++++++ sql/sql_string.cc | 21 +++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/mysql-test/r/func_str.result b/mysql-test/r/func_str.result index 06cfa233f73..5f4f1378657 100644 --- a/mysql-test/r/func_str.result +++ b/mysql-test/r/func_str.result @@ -4757,5 +4757,32 @@ YQ== 61 Yq== 62 DROP TABLE t1; # +# MDEV-18738 ASAN heap-use-after-free in copy_if_not_alloced / copy_fields +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +SELECT REPLACE( CAST( CURDATE() AS BINARY ), CURDATE(), REPEAT('a',32) ) AS f FROM t1 GROUP BY f; +f +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +DROP TABLE t1; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +SELECT REPLACE( LEFT( CURDATE(), 4), LEFT(CURDATE(),4), REPEAT('a',32) ) AS f FROM t1 GROUP BY f; +f +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +DROP TABLE t1; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +SELECT REPLACE(RIGHT(CURDATE(), 4), RIGHT(CURDATE(),4), REPEAT('a',32)) AS f FROM t1 GROUP BY f; +f +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +DROP TABLE t1; +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +SELECT REPLACE(SUBSTR(CURDATE(),2,3), SUBSTR(CURDATE(),2,3), REPEAT('a',32)) AS f FROM t1 GROUP BY f; +f +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +DROP TABLE t1; +# # End of 10.1 tests # diff --git a/mysql-test/t/func_str.test b/mysql-test/t/func_str.test index de954d2f60b..a6be48ad0fa 100644 --- a/mysql-test/t/func_str.test +++ b/mysql-test/t/func_str.test @@ -1877,6 +1877,32 @@ SELECT f1,HEX(f2) FROM t1 WHERE f1='YQ==' AND (f2= from_base64( SELECT f1,HEX(f2) FROM t1 WHERE f1='YQ==' AND (f2= from_base64("Yq==") OR f2= from_base64("YQ==")); DROP TABLE t1; + +--echo # +--echo # MDEV-18738 ASAN heap-use-after-free in copy_if_not_alloced / copy_fields +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +SELECT REPLACE( CAST( CURDATE() AS BINARY ), CURDATE(), REPEAT('a',32) ) AS f FROM t1 GROUP BY f; +DROP TABLE t1; + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +SELECT REPLACE( LEFT( CURDATE(), 4), LEFT(CURDATE(),4), REPEAT('a',32) ) AS f FROM t1 GROUP BY f; +DROP TABLE t1; + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +SELECT REPLACE(RIGHT(CURDATE(), 4), RIGHT(CURDATE(),4), REPEAT('a',32)) AS f FROM t1 GROUP BY f; +DROP TABLE t1; + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +SELECT REPLACE(SUBSTR(CURDATE(),2,3), SUBSTR(CURDATE(),2,3), REPEAT('a',32)) AS f FROM t1 GROUP BY f; +DROP TABLE t1; + + --echo # --echo # End of 10.1 tests --echo # diff --git a/sql/sql_string.cc b/sql/sql_string.cc index c22e33182c6..8c69bea2487 100644 --- a/sql/sql_string.cc +++ b/sql/sql_string.cc @@ -909,6 +909,27 @@ String *copy_if_not_alloced(String *to,String *from,uint32 from_length) (void) from->realloc(from_length); return from; } + if (from->uses_buffer_owned_by(to)) + { + DBUG_ASSERT(!from->alloced); + DBUG_ASSERT(to->alloced); + /* + "from" is a constant string pointing to a fragment of alloced string "to": + to= xxxFFFyyy + - FFF is the part of "to" pointed by "from" + - xxx is the part of "to" before "from" + - yyy is the part of "to" after "from" + */ + uint32 xxx_length= (uint32) (from->ptr() - to->ptr()); + uint32 yyy_length= (uint32) (to->end() - from->end()); + DBUG_ASSERT(to->length() >= yyy_length); + to->length(to->length() - yyy_length); // Remove the "yyy" part + DBUG_ASSERT(to->length() >= xxx_length); + to->replace(0, xxx_length, "", 0); // Remove the "xxx" part + to->realloc(from_length); + to->str_charset= from->str_charset; + return to; + } if (to->realloc(from_length)) return from; // Actually an error if ((to->str_length=MY_MIN(from->str_length,from_length)))