Skip to content
Permalink
Browse files
MDEV-15114 ASAN heap-use-after-free in mem_heap_dup or dfield_data_is…
…_binary_equal

The bug was that innobase_get_computed_value() trashed record[0] and data
in Field_blob::value

Fixed by using a record on the heap for innobase_get_computed_value()

Reviewer: Marko Mäkelä
  • Loading branch information
montywi committed Jun 19, 2018
1 parent 831df10 commit ab19466
Show file tree
Hide file tree
Showing 12 changed files with 376 additions and 26 deletions.
@@ -1,3 +1,6 @@
#
# Test table with a key that consists of indirect virtual fields
#
CREATE TABLE t1 (a int, b int as (a+1) virtual, c int as (b+1) virtual, index(c)) engine=myisam;
insert into t1 (a) values (1),(2),(3);
update t1 set a=5 where a=3;
@@ -36,3 +39,53 @@ a b c
2 3 4
5 6 7
drop table t1;
#
# MDEV-15114
# ASAN heap-use-after-free in mem_heap_dup or
# dfield_data_is_binary_equal
#
CREATE TABLE t1 (
pk INT,
extra tinyint,
c TEXT,
vc LONGTEXT AS (c) VIRTUAL,
i INT,
PRIMARY KEY(pk),
UNIQUE(i),
INDEX(vc(64))
) ENGINE=InnoDB;
INSERT INTO t1 (pk,extra, c, i) VALUES (1, 10, REPEAT('foo ',15000),0);
REPLACE INTO t1 (pk,extra, c,i) SELECT pk,extra+10, c,i FROM t1;
select pk, extra, left(c, 10), length(c), left(vc,10), length(vc), extra from t1;
pk extra left(c, 10) length(c) left(vc,10) length(vc) extra
1 20 foo foo fo 60000 foo foo fo 60000 20
DROP TABLE t1;
#
# Update of deleted row with binary logging enabled
#
SET BINLOG_FORMAT=row;
CREATE TABLE t1 (
pk INT,
c TEXT,
vc LONGTEXT AS (c) VIRTUAL,
i INT,
PRIMARY KEY(pk),
UNIQUE(i),
INDEX(vc(64))
) ENGINE=InnoDB;
INSERT INTO t1 (pk,c,i) VALUES (1,REPEAT('foo ',15000),10);
INSERT INTO t1 (pk,c,i) VALUES (2,REPEAT('bar ',15000),11);
connect c1,localhost,root,,;
connection c1;
begin;
DELETE from t1 WHERE pk=1;
connection default;
update t1 set pk=1 where pk=2;
connection c1;
commit;
connection default;
select pk, left(c, 10), length(c), i from t1;
pk left(c, 10) length(c) i
1 bar bar ba 60000 11
drop table t1;
disconnect c1;
@@ -1,8 +1,9 @@
--source include/have_innodb.inc
--source include/have_log_bin.inc

#
# Test creating table with a key that consists of indirect virtual fields
#
--echo #
--echo # Test table with a key that consists of indirect virtual fields
--echo #

CREATE TABLE t1 (a int, b int as (a+1) virtual, c int as (b+1) virtual, index(c)) engine=myisam;
insert into t1 (a) values (1),(2),(3);
@@ -23,3 +24,58 @@ select * from t1 where c=7;
check table t1;
select * from t1;
drop table t1;

--echo #
--echo # MDEV-15114
--echo # ASAN heap-use-after-free in mem_heap_dup or
--echo # dfield_data_is_binary_equal
--echo #

CREATE TABLE t1 (
pk INT,
extra tinyint,
c TEXT,
vc LONGTEXT AS (c) VIRTUAL,
i INT,
PRIMARY KEY(pk),
UNIQUE(i),
INDEX(vc(64))
) ENGINE=InnoDB;

INSERT INTO t1 (pk,extra, c, i) VALUES (1, 10, REPEAT('foo ',15000),0);
REPLACE INTO t1 (pk,extra, c,i) SELECT pk,extra+10, c,i FROM t1;
select pk, extra, left(c, 10), length(c), left(vc,10), length(vc), extra from t1;
DROP TABLE t1;

--echo #
--echo # Update of deleted row with binary logging enabled
--echo #

SET BINLOG_FORMAT=row;

CREATE TABLE t1 (
pk INT,
c TEXT,
vc LONGTEXT AS (c) VIRTUAL,
i INT,
PRIMARY KEY(pk),
UNIQUE(i),
INDEX(vc(64))
) ENGINE=InnoDB;

INSERT INTO t1 (pk,c,i) VALUES (1,REPEAT('foo ',15000),10);
INSERT INTO t1 (pk,c,i) VALUES (2,REPEAT('bar ',15000),11);

--connect (c1,localhost,root,,)
--connection c1
begin;
DELETE from t1 WHERE pk=1;
--connection default
--send update t1 set pk=1 where pk=2
--connection c1
commit;
--connection default
--reap
select pk, left(c, 10), length(c), i from t1;
drop table t1;
disconnect c1;
@@ -3433,6 +3433,10 @@ class Field_blob :public Field_longstr {
uint32 max_display_length();
uint32 char_length() const;
uint is_equal(Create_field *new_field);

friend void TABLE::remember_blob_values(String *blob_storage);
friend void TABLE::restore_blob_values(String *blob_storage);

private:
int do_save_field_metadata(uchar *first_byte);
};
@@ -2434,6 +2434,8 @@ int TABLE_SHARE::init_from_binary_frm_image(THD *thd, bool write,
reg_field->vcol_info= vcol_info;
share->virtual_fields++;
share->stored_fields--;
if (reg_field->flags & BLOB_FLAG)
share->virtual_not_stored_blob_fields++;
/* Correct stored_rec_length as non stored fields are last */
recpos= (uint) (reg_field->ptr - record);
if (share->stored_rec_length >= recpos)
@@ -6776,6 +6778,52 @@ void TABLE::move_fields(Field **ptr, const uchar *to, const uchar *from)
}


/*
Store all allocated virtual fields blob values
Used by InnoDB when calculating virtual fields for it's own internal
records
*/

void TABLE::remember_blob_values(String *blob_storage)
{
Field **vfield_ptr;
for (vfield_ptr= vfield; *vfield_ptr; vfield_ptr++)
{
if ((*vfield_ptr)->type() == MYSQL_TYPE_BLOB &&
!(*vfield_ptr)->vcol_info->stored_in_db)
{
Field_blob *blob= ((Field_blob*) *vfield_ptr);
memcpy((void*) blob_storage, (void*) &blob->value, sizeof(blob->value));
blob_storage++;
blob->value.release();
}
}
}


/*
Restore all allocated virtual fields blob values
Used by InnoDB when calculating virtual fields for it's own internal
records
*/

void TABLE::restore_blob_values(String *blob_storage)
{
Field **vfield_ptr;
for (vfield_ptr= vfield; *vfield_ptr; vfield_ptr++)
{
if ((*vfield_ptr)->type() == MYSQL_TYPE_BLOB &&
!(*vfield_ptr)->vcol_info->stored_in_db)
{
Field_blob *blob= ((Field_blob*) *vfield_ptr);
blob->value.free();
memcpy((void*) &blob->value, (void*) blob_storage, sizeof(blob->value));
blob_storage++;
}
}
}


/**
@brief
Allocate space for keys
@@ -663,8 +663,11 @@ struct TABLE_SHARE
*/
uint null_bytes_for_compare;
uint fields; /* number of fields */
uint stored_fields; /* number of stored fields, purely virtual not included */
/* number of stored fields, purely virtual not included */
uint stored_fields;
uint virtual_fields; /* number of purely virtual fields */
/* number of purely virtual not stored blobs */
uint virtual_not_stored_blob_fields;
uint null_fields; /* number of null fields */
uint blob_fields; /* number of blob fields */
uint varchar_fields; /* number of varchar fields */
@@ -1423,6 +1426,8 @@ struct TABLE
{ return (my_ptrdiff_t) (s->default_values - record[0]); }

void move_fields(Field **ptr, const uchar *to, const uchar *from);
void remember_blob_values(String *blob_storage);
void restore_blob_values(String *blob_storage);

uint actual_n_key_parts(KEY *keyinfo);
ulong actual_key_flags(KEY *keyinfo);
@@ -21760,6 +21760,77 @@ innobase_get_field_from_update_vector(
return (NULL);
}


/**
Allocate a heap and record for calculating virtual fields
Used mainly for virtual fields in indexes

@param[in] thd MariaDB THD
@param[in] index Index in use
@param[out] heap Heap that holds temporary row
@param[in,out] mysql_table MariaDB table
@param[out] rec Pointer to allocated MariaDB record
@param[out] storage Internal storage for blobs etc

@return FALSE ok
@return TRUE malloc failure
*/

bool innobase_allocate_row_for_vcol(
THD * thd,
dict_index_t* index,
mem_heap_t** heap,
TABLE** table,
byte** record,
VCOL_STORAGE** storage)
{
TABLE *maria_table;
String *blob_value_storage;
if (!*table)
*table= innobase_find_mysql_table_for_vc(thd, index->table);
maria_table= *table;
if (!*heap && !(*heap= mem_heap_create(srv_page_size)))
{
*storage= 0;
return TRUE;
}
*record= static_cast<byte*>(mem_heap_alloc(*heap,
maria_table->s->reclength));
*storage= static_cast<VCOL_STORAGE*>
(mem_heap_alloc(*heap, sizeof(**storage)));
blob_value_storage= static_cast<String*>
(mem_heap_alloc(*heap,
maria_table->s->virtual_not_stored_blob_fields *
sizeof(String)));
if (!*record || !*storage || !blob_value_storage)
{
*storage= 0;
return TRUE;
}
(*storage)->maria_table= maria_table;
(*storage)->innobase_record= *record;
(*storage)->maria_record= maria_table->field[0]->record_ptr();
(*storage)->blob_value_storage= blob_value_storage;

maria_table->move_fields(maria_table->field, *record,
(*storage)->maria_record);
maria_table->remember_blob_values(blob_value_storage);

return FALSE;
}


/** Free memory allocated by innobase_allocate_row_for_vcol() */

void innobase_free_row_for_vcol(VCOL_STORAGE *storage)
{
TABLE *maria_table= storage->maria_table;
maria_table->move_fields(maria_table->field, storage->maria_record,
storage->innobase_record);
maria_table->restore_blob_values(storage->blob_value_storage);
}


/** Get the computed value by supplying the base column values.
@param[in,out] row the data row
@param[in] col virtual column
@@ -21785,12 +21856,12 @@ innobase_get_computed_value(
const dict_field_t* ifield,
THD* thd,
TABLE* mysql_table,
byte* mysql_rec,
const dict_table_t* old_table,
upd_t* parent_update,
dict_foreign_t* foreign)
{
byte rec_buf2[REC_VERSION_56_MAX_INDEX_COL_LEN];
byte* mysql_rec;
byte* buf;
dfield_t* field;
ulint len;
@@ -21803,6 +21874,7 @@ innobase_get_computed_value(

ut_ad(index->table->vc_templ);
ut_ad(thd != NULL);
ut_ad(mysql_table);

const mysql_row_templ_t*
vctempl = index->table->vc_templ->vtempl[
@@ -21820,14 +21892,6 @@ innobase_get_computed_value(
buf = rec_buf2;
}

if (!mysql_table) {
mysql_table = innobase_find_mysql_table_for_vc(thd, index->table);
}

ut_ad(mysql_table);

mysql_rec = mysql_table->record[0];

for (ulint i = 0; i < col->num_base; i++) {
dict_col_t* base_col = col->base_col[i];
const dfield_t* row_field = NULL;
@@ -846,6 +846,44 @@ struct SysIndexCallback {
virtual void operator()(mtr_t* mtr, btr_pcur_t* pcur) throw() = 0;
};


/** Storage for calculating virtual columns */

class String;
struct VCOL_STORAGE
{
TABLE *maria_table;
byte *innobase_record;
byte *maria_record;
String *blob_value_storage;
};

/**
Allocate a heap and record for calculating virtual fields
Used mainly for virtual fields in indexes
@param[in] thd MariaDB THD
@param[in] index Index in use
@param[out] heap Heap that holds temporary row
@param[in,out] mysql_table MariaDB table
@param[out] rec Pointer to allocated MariaDB record
@param[out] storage Internal storage for blobs etc
@return FALSE ok
@return TRUE malloc failure
*/

bool innobase_allocate_row_for_vcol(
THD * thd,
dict_index_t* index,
mem_heap_t** heap,
TABLE** table,
byte** record,
VCOL_STORAGE** storage);

/** Free memory allocated by innobase_allocate_row_for_vcol() */
void innobase_free_row_for_vcol(VCOL_STORAGE *storage);

/** Get the computed value by supplying the base column values.
@param[in,out] row the data row
@param[in] col virtual column
@@ -870,6 +908,7 @@ innobase_get_computed_value(
const dict_field_t* ifield,
THD* thd,
TABLE* mysql_table,
byte* mysql_rec,
const dict_table_t* old_table,
upd_t* parent_update,
dict_foreign_t* foreign);

0 comments on commit ab19466

Please sign in to comment.