summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Midenkov <midenok@gmail.com>2020-08-17 00:35:48 +0300
committerAleksey Midenkov <midenok@gmail.com>2021-04-23 12:17:53 +0300
commit62c0e3588abb5a467765769bfc2f9256eca810ae (patch)
treec2ae850034a5528b57b0f19f34465b42a0f95c1c
parent40d4ea1e748f27c5adcc39f8f07f5e786458d0c2 (diff)
downloadmariadb-git-62c0e3588abb5a467765769bfc2f9256eca810ae.tar.gz
MDEV-18734 ASAN heap-use-after-free upon sorting by blob column from partitioned table
ha_partition stores records in array of m_ordered_rec_buffer and uses it for prio queue in ordered index scan. When the records are restored from the array the blob buffers may be already freed or rewritten. The solution is to copy record blobs into local memory pool m_ordered_root when the record is stored into m_ordered_rec_buffer. When the record is restored from m_ordered_rec_buffer the table fields are made to own blob buffers. m_ordered_root like m_ordered_rec_buffer (which is now allocated from it) is freed upon destroy_record_priority_queue(). This the subject for memory leak when processing infinite amount of rows. Then next patch solves this problem via per-element memory pools.
-rw-r--r--mysql-test/suite/federated/federated_partition.result36
-rw-r--r--mysql-test/suite/federated/federated_partition.test25
-rw-r--r--mysql-test/suite/vcol/r/partition.result73
-rw-r--r--mysql-test/suite/vcol/t/partition.test48
-rw-r--r--sql/field.h24
-rw-r--r--sql/ha_partition.cc50
-rw-r--r--sql/ha_partition.h2
7 files changed, 253 insertions, 5 deletions
diff --git a/mysql-test/suite/federated/federated_partition.result b/mysql-test/suite/federated/federated_partition.result
index a2d5fcffd9b..cce5e3cf3e6 100644
--- a/mysql-test/suite/federated/federated_partition.result
+++ b/mysql-test/suite/federated/federated_partition.result
@@ -46,6 +46,42 @@ connection slave;
drop table federated.t1_1;
drop table federated.t1_2;
End of 5.1 tests
+#
+# MDEV-18734 ASAN heap-use-after-free upon sorting by blob column from partitioned table
+#
+connection slave;
+use federated;
+create table t1_1 (x int, b text, key(x));
+create table t1_2 (x int, b text, key(x));
+connection master;
+create table t1 (x int, b text, key(x)) engine=federated
+partition by range columns (x) (
+partition p1 values less than (40) connection='mysql://root@127.0.0.1:SLAVE_PORT/federated/t1_1',
+partition pn values less than (maxvalue) connection='mysql://root@127.0.0.1:SLAVE_PORT/federated/t1_2'
+);
+insert t1 values (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8);
+insert t1 select x + 8, x + 8 from t1;
+insert t1 select x + 16, x + 16 from t1;
+insert t1 select x + 49, repeat(x + 49, 100) from t1;
+flush tables;
+# This produces wrong result before MDEV-17573
+select x, left(b, 10) from t1 where x > 30 and x < 60 order by b;
+x left(b, 10)
+31 31
+32 32
+50 5050505050
+51 5151515151
+52 5252525252
+53 5353535353
+54 5454545454
+55 5555555555
+56 5656565656
+57 5757575757
+58 5858585858
+59 5959595959
+drop table t1;
+connection slave;
+drop table t1_1, t1_2;
connection master;
DROP TABLE IF EXISTS federated.t1;
DROP DATABASE IF EXISTS federated;
diff --git a/mysql-test/suite/federated/federated_partition.test b/mysql-test/suite/federated/federated_partition.test
index ef1e27ec505..c5c8433b4cf 100644
--- a/mysql-test/suite/federated/federated_partition.test
+++ b/mysql-test/suite/federated/federated_partition.test
@@ -50,4 +50,29 @@ drop table federated.t1_2;
--echo End of 5.1 tests
+--echo #
+--echo # MDEV-18734 ASAN heap-use-after-free upon sorting by blob column from partitioned table
+--echo #
+connection slave;
+use federated;
+create table t1_1 (x int, b text, key(x));
+create table t1_2 (x int, b text, key(x));
+connection master;
+--replace_result $SLAVE_MYPORT SLAVE_PORT
+eval create table t1 (x int, b text, key(x)) engine=federated
+ partition by range columns (x) (
+ partition p1 values less than (40) connection='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t1_1',
+ partition pn values less than (maxvalue) connection='mysql://root@127.0.0.1:$SLAVE_MYPORT/federated/t1_2'
+);
+insert t1 values (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8);
+insert t1 select x + 8, x + 8 from t1;
+insert t1 select x + 16, x + 16 from t1;
+insert t1 select x + 49, repeat(x + 49, 100) from t1;
+flush tables;
+--echo # This produces wrong result before MDEV-17573
+select x, left(b, 10) from t1 where x > 30 and x < 60 order by b;
+drop table t1;
+connection slave;
+drop table t1_1, t1_2;
+
source include/federated_cleanup.inc;
diff --git a/mysql-test/suite/vcol/r/partition.result b/mysql-test/suite/vcol/r/partition.result
index bd1353fa145..d7c5052b72a 100644
--- a/mysql-test/suite/vcol/r/partition.result
+++ b/mysql-test/suite/vcol/r/partition.result
@@ -28,3 +28,76 @@ set statement sql_mode= '' for update t1 set i= 1, v= 2;
Warnings:
Warning 1906 The value specified for generated column 'v' in table 't1' has been ignored
drop table t1;
+#
+# MDEV-18734 ASAN heap-use-after-free in my_strnxfrm_simple_internal upon update on versioned partitioned table
+#
+# Cover queue_fix() in ha_partition::handle_ordered_index_scan()
+create or replace table t1 (
+x int auto_increment primary key,
+b text, v mediumtext as (b) virtual,
+index (v(10))
+) partition by range columns (x) (
+partition p1 values less than (3),
+partition p2 values less than (6),
+partition p3 values less than (9),
+partition p4 values less than (12),
+partition p5 values less than (15),
+partition p6 values less than (17),
+partition p7 values less than (19),
+partition p8 values less than (21),
+partition p9 values less than (23),
+partition p10 values less than (25),
+partition p11 values less than (27),
+partition p12 values less than (29),
+partition p13 values less than (31),
+partition p14 values less than (33),
+partition p15 values less than (35),
+partition pn values less than (maxvalue));
+insert into t1 (b) values
+(repeat('q', 8192)), (repeat('z', 8192)), (repeat('a', 8192)), (repeat('b', 8192)),
+(repeat('x', 8192)), (repeat('y', 8192));
+insert t1 (b) select b from t1;
+insert t1 (b) select b from t1;
+insert t1 (b) select b from t1;
+insert t1 (b) select b from t1;
+select x, left(b, 10), left(v, 10) from t1 where x > 30 and x < 60 order by v;
+x left(b, 10) left(v, 10)
+33 aaaaaaaaaa aaaaaaaaaa
+39 aaaaaaaaaa aaaaaaaaaa
+45 aaaaaaaaaa aaaaaaaaaa
+51 aaaaaaaaaa aaaaaaaaaa
+57 aaaaaaaaaa aaaaaaaaaa
+34 bbbbbbbbbb bbbbbbbbbb
+40 bbbbbbbbbb bbbbbbbbbb
+46 bbbbbbbbbb bbbbbbbbbb
+52 bbbbbbbbbb bbbbbbbbbb
+58 bbbbbbbbbb bbbbbbbbbb
+31 qqqqqqqqqq qqqqqqqqqq
+37 qqqqqqqqqq qqqqqqqqqq
+43 qqqqqqqqqq qqqqqqqqqq
+49 qqqqqqqqqq qqqqqqqqqq
+55 qqqqqqqqqq qqqqqqqqqq
+35 xxxxxxxxxx xxxxxxxxxx
+41 xxxxxxxxxx xxxxxxxxxx
+47 xxxxxxxxxx xxxxxxxxxx
+53 xxxxxxxxxx xxxxxxxxxx
+59 xxxxxxxxxx xxxxxxxxxx
+36 yyyyyyyyyy yyyyyyyyyy
+42 yyyyyyyyyy yyyyyyyyyy
+48 yyyyyyyyyy yyyyyyyyyy
+54 yyyyyyyyyy yyyyyyyyyy
+32 zzzzzzzzzz zzzzzzzzzz
+38 zzzzzzzzzz zzzzzzzzzz
+44 zzzzzzzzzz zzzzzzzzzz
+50 zzzzzzzzzz zzzzzzzzzz
+56 zzzzzzzzzz zzzzzzzzzz
+update t1 set b= 'bar' where v > 'a' limit 20;
+drop table t1;
+# Cover return_top_record() in ha_partition::handle_ordered_index_scan()
+create table t1 (x int primary key, b tinytext, v text as (b) virtual)
+partition by range columns (x) (
+partition p1 values less than (4),
+partition pn values less than (maxvalue));
+insert into t1 (x, b) values (1, ''), (2, ''), (3, 'a'), (4, 'b');
+update t1 set b= 'bar' where x > 0 order by v limit 2;
+drop table t1;
diff --git a/mysql-test/suite/vcol/t/partition.test b/mysql-test/suite/vcol/t/partition.test
index 889724fb1c5..408990b20a6 100644
--- a/mysql-test/suite/vcol/t/partition.test
+++ b/mysql-test/suite/vcol/t/partition.test
@@ -30,3 +30,51 @@ subpartition by hash(v) subpartitions 3 (
insert t1 set i= 0;
set statement sql_mode= '' for update t1 set i= 1, v= 2;
drop table t1;
+
+--echo #
+--echo # MDEV-18734 ASAN heap-use-after-free in my_strnxfrm_simple_internal upon update on versioned partitioned table
+--echo #
+--echo # Cover queue_fix() in ha_partition::handle_ordered_index_scan()
+create or replace table t1 (
+ x int auto_increment primary key,
+ b text, v mediumtext as (b) virtual,
+ index (v(10))
+) partition by range columns (x) (
+ partition p1 values less than (3),
+ partition p2 values less than (6),
+ partition p3 values less than (9),
+ partition p4 values less than (12),
+ partition p5 values less than (15),
+ partition p6 values less than (17),
+ partition p7 values less than (19),
+ partition p8 values less than (21),
+ partition p9 values less than (23),
+ partition p10 values less than (25),
+ partition p11 values less than (27),
+ partition p12 values less than (29),
+ partition p13 values less than (31),
+ partition p14 values less than (33),
+ partition p15 values less than (35),
+ partition pn values less than (maxvalue));
+insert into t1 (b) values
+(repeat('q', 8192)), (repeat('z', 8192)), (repeat('a', 8192)), (repeat('b', 8192)),
+(repeat('x', 8192)), (repeat('y', 8192));
+
+insert t1 (b) select b from t1;
+insert t1 (b) select b from t1;
+insert t1 (b) select b from t1;
+insert t1 (b) select b from t1;
+
+select x, left(b, 10), left(v, 10) from t1 where x > 30 and x < 60 order by v;
+update t1 set b= 'bar' where v > 'a' limit 20;
+
+drop table t1;
+
+--echo # Cover return_top_record() in ha_partition::handle_ordered_index_scan()
+create table t1 (x int primary key, b tinytext, v text as (b) virtual)
+partition by range columns (x) (
+ partition p1 values less than (4),
+ partition pn values less than (maxvalue));
+insert into t1 (x, b) values (1, ''), (2, ''), (3, 'a'), (4, 'b');
+update t1 set b= 'bar' where x > 0 order by v limit 2;
+drop table t1;
diff --git a/sql/field.h b/sql/field.h
index 18e44f1d9d4..63f0298a9ab 100644
--- a/sql/field.h
+++ b/sql/field.h
@@ -3464,6 +3464,12 @@ public:
uchar *new_ptr, uint32 length,
uchar *new_null_ptr, uint new_null_bit);
void sql_type(String &str) const;
+ /**
+ Copy blob buffer into internal storage "value" and update record pointer.
+
+ @retval true Memory allocation error
+ @retval false Success
+ */
inline bool copy()
{
uchar *tmp= get_ptr();
@@ -3476,6 +3482,24 @@ public:
memcpy(ptr+packlength, &tmp, sizeof(char*));
return 0;
}
+ /**
+ Copy blob buffer into mem_root-allocated buffer and update record pointer.
+
+ @param mem_root Memory pool to allocate from
+ @retval true Memory allocation error
+ @retval false Success
+ */
+ bool copy(MEM_ROOT *mem_root)
+ {
+ uchar *src= get_ptr();
+ uint len= get_length();
+ uchar *dst= (uchar *) alloc_root(mem_root, len);
+ if (!dst)
+ return true;
+ memcpy(dst, src, len);
+ memcpy(ptr + packlength, &dst, sizeof(char*));
+ return false;
+ }
/* store value for the duration of the current read record */
inline void swap_value_and_read_value()
{
diff --git a/sql/ha_partition.cc b/sql/ha_partition.cc
index aa30df0db71..dfd29f9a799 100644
--- a/sql/ha_partition.cc
+++ b/sql/ha_partition.cc
@@ -5132,7 +5132,9 @@ bool ha_partition::init_record_priority_queue()
/* Allocate a key for temporary use when setting up the scan. */
alloc_len+= table_share->max_key_length;
- if (!(m_ordered_rec_buffer= (uchar*)my_malloc(alloc_len, MYF(MY_WME))))
+ init_alloc_root(&m_ordered_root, 512, 0, MYF(MY_WME));
+
+ if (!(m_ordered_rec_buffer= (uchar*) alloc_root(&m_ordered_root, alloc_len)))
DBUG_RETURN(true);
/*
@@ -5169,7 +5171,7 @@ bool ha_partition::init_record_priority_queue()
}
if (init_queue(&m_queue, used_parts, 0, 0, cmp_func, cmp_arg, 0, 0))
{
- my_free(m_ordered_rec_buffer);
+ free_root(&m_ordered_root, MYF(MY_WME));
m_ordered_rec_buffer= NULL;
DBUG_RETURN(true);
}
@@ -5187,7 +5189,7 @@ void ha_partition::destroy_record_priority_queue()
if (m_ordered_rec_buffer)
{
delete_queue(&m_queue);
- my_free(m_ordered_rec_buffer);
+ free_root(&m_ordered_root, MYF(MY_WME));
m_ordered_rec_buffer= NULL;
}
DBUG_VOID_RETURN;
@@ -6178,7 +6180,11 @@ int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order)
*/
error= file->read_range_first(m_start_key.key? &m_start_key: NULL,
end_range, eq_range, TRUE);
- memcpy(rec_buf_ptr, table->record[0], m_rec_length);
+ if (!error)
+ {
+ memcpy(rec_buf_ptr, table->record[0], m_rec_length);
+ }
+
reverse_order= FALSE;
break;
}
@@ -6198,6 +6204,10 @@ int ha_partition::handle_ordered_index_scan(uchar *buf, bool reverse_order)
Initialize queue without order first, simply insert
*/
queue_element(&m_queue, j++)= part_rec_buf_ptr;
+ if (copy_blobs(rec_buf_ptr, &m_ordered_root))
+ {
+ error= HA_ERR_OUT_OF_MEM;
+ }
}
else if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
{
@@ -6250,6 +6260,7 @@ void ha_partition::return_top_record(uchar *buf)
part_id= uint2korr(key_buffer);
memcpy(buf, rec_buffer, m_rec_length);
+ copy_blobs(buf, NULL);
m_last_part= part_id;
m_top_entry= part_id;
}
@@ -6310,6 +6321,28 @@ int ha_partition::handle_ordered_index_scan_key_not_found()
}
+bool ha_partition::copy_blobs(uchar * rec_buf, MEM_ROOT *mem_root)
+{
+ bool err= false;
+ table->move_fields(table->field, rec_buf, table->record[0]);
+ for (Field **f= table->field; *f; f++)
+ {
+ if (!((*f)->flags & BLOB_FLAG) ||
+ !bitmap_is_set(table->read_set, (*f)->field_index))
+ continue;
+ Field_blob *b= static_cast<Field_blob *>(*f);
+ if (mem_root)
+ err= b->copy(&m_ordered_root);
+ else
+ err= b->copy();
+ if (err)
+ break;
+ }
+ table->move_fields(table->field, table->record[0], rec_buf);
+ return err;
+}
+
+
/*
Common routine to handle index_next with ordered results
@@ -6371,7 +6404,14 @@ int ha_partition::handle_ordered_next(uchar *buf, bool is_next_same)
if (m_index_scan_type == partition_read_range)
{
error= file->read_range_next();
- memcpy(rec_buf, table->record[0], m_rec_length);
+ if (!error)
+ {
+ memcpy(rec_buf, table->record[0], m_rec_length);
+ if (copy_blobs(rec_buf, &m_ordered_root))
+ {
+ error= HA_ERR_OUT_OF_MEM;
+ }
+ }
}
else if (!is_next_same)
error= file->ha_index_next(rec_buf);
diff --git a/sql/ha_partition.h b/sql/ha_partition.h
index a48aa639237..b3f22dde3d3 100644
--- a/sql/ha_partition.h
+++ b/sql/ha_partition.h
@@ -141,6 +141,7 @@ private:
partition_info *m_part_info; // local reference to partition
Field **m_part_field_array; // Part field array locally to save acc
uchar *m_ordered_rec_buffer; // Row and key buffer for ord. idx scan
+ MEM_ROOT m_ordered_root;
/*
Current index.
When used in key_rec_cmp: If clustered pk, index compare
@@ -635,6 +636,7 @@ private:
int handle_ordered_next(uchar * buf, bool next_same);
int handle_ordered_prev(uchar * buf);
void return_top_record(uchar * buf);
+ bool copy_blobs(uchar * rec_buf, MEM_ROOT * mem_root);
public:
/*
-------------------------------------------------------------------------