mirror of
https://github.com/MariaDB/server.git
synced 2025-12-28 08:10:14 +00:00
MDEV-35816 ASAN use-after-poison in st_select_lex::print
For prepared statements with derived tables defined by CTEs, and during second execution, there is a dangling pointer to an instance of a JOIN object that no longer exists. Normally, the join member of a SELECT_LEX is freed during a call to st_select_lex::cleanup() which recursively traverses the query tree. But due to CTE merging during mysql_derived_merge, the unit containing the SELECT_LEX that would've been freed is cutoff from the query tree. We now remember all such units in a linked list so that they're cleaned up at the end of the lifetime of the query.
This commit is contained in:
parent
aec79c5a79
commit
34a8209d66
@ -3930,3 +3930,43 @@ drop table tm, t;
|
||||
#
|
||||
# End of 10.8 tests
|
||||
#
|
||||
#
|
||||
# MDEV-35816 ASAN use-after-poison in st_select_lex::print
|
||||
#
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5);
|
||||
SET SESSION optimizer_trace = 'enabled=on';
|
||||
PREPARE stmt FROM 'SELECT STRAIGHT_JOIN * FROM t1 WHERE a IN (WITH cte AS (SELECT a FROM t1) SELECT * FROM cte)';
|
||||
EXECUTE stmt;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
EXECUTE stmt;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
PREPARE nested FROM 'SELECT STRAIGHT_JOIN * FROM t1 WHERE a IN (WITH cte AS (WITH cte2 AS (SELECT a FROM t1) SELECT * from cte2) SELECT * FROM cte)';
|
||||
EXECUTE nested;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
EXECUTE nested;
|
||||
a
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
DROP TABLE t1;
|
||||
#
|
||||
# End of 10.11 tests
|
||||
#
|
||||
|
||||
@ -2887,3 +2887,21 @@ drop table tm, t;
|
||||
--echo #
|
||||
--echo # End of 10.8 tests
|
||||
--echo #
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-35816 ASAN use-after-poison in st_select_lex::print
|
||||
--echo #
|
||||
CREATE TABLE t1 (a INT);
|
||||
INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5);
|
||||
SET SESSION optimizer_trace = 'enabled=on';
|
||||
PREPARE stmt FROM 'SELECT STRAIGHT_JOIN * FROM t1 WHERE a IN (WITH cte AS (SELECT a FROM t1) SELECT * FROM cte)';
|
||||
EXECUTE stmt;
|
||||
EXECUTE stmt;
|
||||
PREPARE nested FROM 'SELECT STRAIGHT_JOIN * FROM t1 WHERE a IN (WITH cte AS (WITH cte2 AS (SELECT a FROM t1) SELECT * from cte2) SELECT * FROM cte)';
|
||||
EXECUTE nested;
|
||||
EXECUTE nested;
|
||||
DROP TABLE t1;
|
||||
|
||||
--echo #
|
||||
--echo # End of 10.11 tests
|
||||
--echo #
|
||||
|
||||
@ -2959,6 +2959,41 @@ void st_select_lex_node::init_query_common()
|
||||
uncacheable= 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
We need to remember this unit for cleanup after it is stranded during CTE
|
||||
merge (see mysql_derived_merge). Walk to the root unit of this query tree
|
||||
(the root unit lifetime extends for the entire query) and insert myself
|
||||
into the front of the stranded_clean_list:
|
||||
before: root -> B -> A
|
||||
after: root -> this -> B -> A
|
||||
During cleanup, the stranded units are cleaned in FIFO order.
|
||||
*/
|
||||
void st_select_lex_unit::remember_my_cleanup()
|
||||
{
|
||||
// Walk to the root unit (which lives until the end of the query) ...
|
||||
st_select_lex_node *root= this;
|
||||
while (root->master)
|
||||
root= root->master;
|
||||
|
||||
// ... and add myself to the front of the stranded_clean_list.
|
||||
st_select_lex_unit *unit= static_cast<st_select_lex_unit*>(root);
|
||||
st_select_lex_unit *prior_head= unit->stranded_clean_list;
|
||||
unit->stranded_clean_list= this;
|
||||
stranded_clean_list= prior_head;
|
||||
}
|
||||
|
||||
|
||||
void st_select_lex_unit::cleanup_stranded_units()
|
||||
{
|
||||
if (!stranded_clean_list)
|
||||
return;
|
||||
|
||||
stranded_clean_list->cleanup();
|
||||
stranded_clean_list= nullptr;
|
||||
}
|
||||
|
||||
|
||||
void st_select_lex_unit::init_query()
|
||||
{
|
||||
init_query_common();
|
||||
@ -3384,6 +3419,7 @@ void st_select_lex_unit::exclude_level()
|
||||
}
|
||||
// Mark it excluded
|
||||
prev= NULL;
|
||||
remember_my_cleanup();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -862,6 +862,28 @@ bool print_explain_for_slow_log(LEX *lex, THD *thd, String *str);
|
||||
|
||||
|
||||
class st_select_lex_unit: public st_select_lex_node {
|
||||
private:
|
||||
/*
|
||||
When a CTE is merged to the parent SELECT, its unit is excluded
|
||||
which separates it from the tree of units for this query. It
|
||||
needs to be cleaned up but not at the time it is excluded, since
|
||||
its queries are merged to the unit above it. Remember all such
|
||||
units via the stranded_clean_list and clean them at the end of
|
||||
the query. This list is maintained only at the root unit node
|
||||
of the query tree.
|
||||
*/
|
||||
st_select_lex_unit *stranded_clean_list{nullptr};
|
||||
|
||||
// Add myself to the stranded_clean_list.
|
||||
void remember_my_cleanup();
|
||||
|
||||
/*
|
||||
Walk the stranded_clean_list and cleanup units. This must only
|
||||
be called for the st_select_lex_unit type because it assumes
|
||||
that those are the only nodes in the stranded_clean_list.
|
||||
*/
|
||||
void cleanup_stranded_units();
|
||||
|
||||
protected:
|
||||
TABLE_LIST result_table_list;
|
||||
select_unit *union_result;
|
||||
|
||||
@ -2598,6 +2598,8 @@ err:
|
||||
|
||||
bool st_select_lex_unit::cleanup()
|
||||
{
|
||||
cleanup_stranded_units();
|
||||
|
||||
bool error= 0;
|
||||
DBUG_ENTER("st_select_lex_unit::cleanup");
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user