Commit af784385 authored by Monty's avatar Monty

Fix for using uninitialized memory

MDEV-22073 MSAN use-of-uninitialized-value in
collect_statistics_for_table()

Other things:
innodb.analyze_table was changed to mainly test statistic
collection. Was discussed with Marko.
parent 277aa85c
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
#if defined(HAVE_VALGRIND_MEMCHECK_H) && defined(HAVE_valgrind) #if defined(HAVE_VALGRIND_MEMCHECK_H) && defined(HAVE_valgrind)
# include <valgrind/memcheck.h> # include <valgrind/memcheck.h>
# define MEM_UNDEFINED(a,len) VALGRIND_MAKE_MEM_UNDEFINED(a,len) # define MEM_UNDEFINED(a,len) VALGRIND_MAKE_MEM_UNDEFINED(a,len)
# define MEM_MAKE_DEFINED(a,len) VALGRIND_MAKE_MEM_DEFINED(a,len)
# define MEM_NOACCESS(a,len) VALGRIND_MAKE_MEM_NOACCESS(a,len) # define MEM_NOACCESS(a,len) VALGRIND_MAKE_MEM_NOACCESS(a,len)
# define MEM_CHECK_ADDRESSABLE(a,len) VALGRIND_CHECK_MEM_IS_ADDRESSABLE(a,len) # define MEM_CHECK_ADDRESSABLE(a,len) VALGRIND_CHECK_MEM_IS_ADDRESSABLE(a,len)
# define MEM_CHECK_DEFINED(a,len) VALGRIND_CHECK_MEM_IS_DEFINED(a,len) # define MEM_CHECK_DEFINED(a,len) VALGRIND_CHECK_MEM_IS_DEFINED(a,len)
...@@ -42,12 +43,22 @@ ...@@ -42,12 +43,22 @@
/* How to do manual poisoning: /* How to do manual poisoning:
https://github.com/google/sanitizers/wiki/AddressSanitizerManualPoisoning */ https://github.com/google/sanitizers/wiki/AddressSanitizerManualPoisoning */
# define MEM_UNDEFINED(a,len) ASAN_UNPOISON_MEMORY_REGION(a,len) # define MEM_UNDEFINED(a,len) ASAN_UNPOISON_MEMORY_REGION(a,len)
# define MEM_MAKE_DEFINED(a,len) ((void) 0)
# define MEM_NOACCESS(a,len) ASAN_POISON_MEMORY_REGION(a,len) # define MEM_NOACCESS(a,len) ASAN_POISON_MEMORY_REGION(a,len)
# define MEM_CHECK_ADDRESSABLE(a,len) ((void) 0) # define MEM_CHECK_ADDRESSABLE(a,len) ((void) 0)
# define MEM_CHECK_DEFINED(a,len) ((void) 0) # define MEM_CHECK_DEFINED(a,len) ((void) 0)
# define REDZONE_SIZE 8 # define REDZONE_SIZE 8
#elif __has_feature(memory_sanitizer)
# include <sanitizer/msan_interface.h>
# define MEM_UNDEFINED(a,len) __msan_allocated_memory(a,len)
# define MEM_MAKE_DEFINED(a,len) __msan_unpoison(a,len)
# define MEM_NOACCESS(a,len) ((void) 0)
# define MEM_CHECK_ADDRESSABLE(a,len) ((void) 0)
# define MEM_CHECK_DEFINED(a,len) __msan_check_mem_is_initialized(a,len)
# define REDZONE_SIZE 8
#else #else
# define MEM_UNDEFINED(a,len) ((void) (a), (void) (len)) # define MEM_UNDEFINED(a,len) ((void) (a), (void) (len))
# define MEM_MAKE_DEFINED(a,len) ((void) 0)
# define MEM_NOACCESS(a,len) ((void) 0) # define MEM_NOACCESS(a,len) ((void) 0)
# define MEM_CHECK_ADDRESSABLE(a,len) ((void) 0) # define MEM_CHECK_ADDRESSABLE(a,len) ((void) 0)
# define MEM_CHECK_DEFINED(a,len) ((void) 0) # define MEM_CHECK_DEFINED(a,len) ((void) 0)
...@@ -55,12 +66,15 @@ ...@@ -55,12 +66,15 @@
#endif /* HAVE_VALGRIND_MEMCHECK_H */ #endif /* HAVE_VALGRIND_MEMCHECK_H */
#ifndef DBUG_OFF #ifndef DBUG_OFF
/* NOTE: Do not invoke TRASH_FILL directly! Use TRASH_ALLOC or TRASH_FREE. /*
TRASH_FILL() has to call MEM_UNDEFINED() to cancel any effect of TRASH_FREE().
The MEM_UNDEFINED() call before memset() is for canceling the effect This can happen in the case one does
of any previous MEM_NOACCESS(). We must invoke MEM_UNDEFINED() after TRASH_ALLOC(A,B) ; TRASH_FREE(A,B) ; TRASH_ALLOC(A,B)
writing the dummy pattern, unless MEM_NOACCESS() is going to be invoked. to reuse the same memory in an internal memory allocator like MEM_ROOT.
On AddressSanitizer, the MEM_UNDEFINED() in TRASH_ALLOC() has no effect. */ For my_malloc() and safemalloc() the extra MEM_UNDEFINED is bit of an
overkill.
TRASH_FILL() is an internal function and should not be used externally.
*/
#define TRASH_FILL(A,B,C) do { const size_t trash_tmp= (B); MEM_UNDEFINED(A, trash_tmp); memset(A, C, trash_tmp); } while (0) #define TRASH_FILL(A,B,C) do { const size_t trash_tmp= (B); MEM_UNDEFINED(A, trash_tmp); memset(A, C, trash_tmp); } while (0)
#else #else
#define TRASH_FILL(A,B,C) do { MEM_UNDEFINED((A), (B)); } while (0) #define TRASH_FILL(A,B,C) do { MEM_UNDEFINED((A), (B)); } while (0)
......
CREATE PROCEDURE populate_t1() set use_stat_tables='preferably';
BEGIN
DECLARE i int DEFAULT 1;
START TRANSACTION;
WHILE (i <= 1000000) DO
INSERT INTO t1 VALUES (i, i, CONCAT('a', i));
SET i = i + 1;
END WHILE;
COMMIT;
END|
CREATE TABLE t1( CREATE TABLE t1(
class INT, class INT,
id INT, id INT,
title VARCHAR(100) title VARCHAR(100)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
insert into t1 select seq, seq, concat('a', seq) from seq_1_to_500;
SELECT COUNT(*) FROM t1; SELECT COUNT(*) FROM t1;
COUNT(*) COUNT(*)
1000000 500
SET GLOBAL innodb_stats_persistent_sample_pages=2000; set @@max_heap_table_size=16384;
ANALYZE TABLE t1; ANALYZE TABLE t1;
Table Op Msg_type Msg_text Table Op Msg_type Msg_text
test.t1 analyze status Engine-independent statistics collected
test.t1 analyze status OK test.t1 analyze status OK
DROP TABLE t1; DROP TABLE t1;
DROP PROCEDURE populate_t1;
SET GLOBAL innodb_stats_persistent_sample_pages=default;
#
# BUG#22385442 - INNODB: DIFFICULT TO FIND FREE BLOCKS IN THE BUFFER POOL
#
--source include/have_innodb.inc --source include/have_innodb.inc
--source include/big_test.inc --source include/have_sequence.inc
DELIMITER |; #
CREATE PROCEDURE populate_t1() # MDEV-22073 MSAN use-of-uninitialized-value in collect_statistics_for_table()
BEGIN #
DECLARE i int DEFAULT 1;
START TRANSACTION; set use_stat_tables='preferably';
WHILE (i <= 1000000) DO
INSERT INTO t1 VALUES (i, i, CONCAT('a', i));
SET i = i + 1;
END WHILE;
COMMIT;
END|
DELIMITER ;|
CREATE TABLE t1( CREATE TABLE t1(
class INT, class INT,
...@@ -25,18 +13,11 @@ CREATE TABLE t1( ...@@ -25,18 +13,11 @@ CREATE TABLE t1(
title VARCHAR(100) title VARCHAR(100)
) ENGINE=InnoDB; ) ENGINE=InnoDB;
-- disable_query_log insert into t1 select seq, seq, concat('a', seq) from seq_1_to_500;
CALL populate_t1();
-- enable_query_log
SELECT COUNT(*) FROM t1; SELECT COUNT(*) FROM t1;
SET GLOBAL innodb_stats_persistent_sample_pages=2000; set @@max_heap_table_size=16384;
ANALYZE TABLE t1; ANALYZE TABLE t1;
DROP TABLE t1; DROP TABLE t1;
DROP PROCEDURE populate_t1;
SET GLOBAL innodb_stats_persistent_sample_pages=default;
...@@ -7772,6 +7772,12 @@ my_decimal *Field_varstring::val_decimal(my_decimal *decimal_value) ...@@ -7772,6 +7772,12 @@ my_decimal *Field_varstring::val_decimal(my_decimal *decimal_value)
} }
void Field_varstring::mark_unused_memory_as_defined()
{
uint used_length __attribute__((unused)) = get_length();
MEM_MAKE_DEFINED(get_data() + used_length, field_length - used_length);
}
int Field_varstring::cmp_max(const uchar *a_ptr, const uchar *b_ptr, int Field_varstring::cmp_max(const uchar *a_ptr, const uchar *b_ptr,
uint max_len) uint max_len)
......
...@@ -850,6 +850,14 @@ class Field: public Value_source ...@@ -850,6 +850,14 @@ class Field: public Value_source
enum_check_fields check_level); enum_check_fields check_level);
int store(const LEX_STRING *ls, CHARSET_INFO *cs) int store(const LEX_STRING *ls, CHARSET_INFO *cs)
{ return store(ls->str, (uint32) ls->length, cs); } { return store(ls->str, (uint32) ls->length, cs); }
/*
Mark unused memory in the field as defined. Mainly used to ensure
that if we write full field to disk (for example in
Count_distinct_field::add(), we don't write unitalized data to
disk which would confuse valgrind or MSAN.
*/
virtual void mark_unused_memory_as_defined() {}
virtual double val_real(void)=0; virtual double val_real(void)=0;
virtual longlong val_int(void)=0; virtual longlong val_int(void)=0;
/* /*
...@@ -3242,6 +3250,7 @@ class Field_varstring :public Field_longstr { ...@@ -3242,6 +3250,7 @@ class Field_varstring :public Field_longstr {
int store(const char *to,uint length,CHARSET_INFO *charset); int store(const char *to,uint length,CHARSET_INFO *charset);
int store(longlong nr, bool unsigned_val); int store(longlong nr, bool unsigned_val);
int store(double nr) { return Field_str::store(nr); } /* QQ: To be deleted */ int store(double nr) { return Field_str::store(nr); } /* QQ: To be deleted */
void mark_unused_memory_as_defined();
double val_real(void); double val_real(void);
longlong val_int(void); longlong val_int(void);
String *val_str(String*,String *); String *val_str(String*,String *);
......
...@@ -1671,6 +1671,7 @@ class Count_distinct_field: public Sql_alloc ...@@ -1671,6 +1671,7 @@ class Count_distinct_field: public Sql_alloc
*/ */
virtual bool add() virtual bool add()
{ {
table_field->mark_unused_memory_as_defined();
return tree->unique_add(table_field->ptr); return tree->unique_add(table_field->ptr);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment