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:
Dave Gosselin 2025-09-19 13:17:06 -04:00 committed by Dave Gosselin
parent aec79c5a79
commit 34a8209d66
5 changed files with 118 additions and 0 deletions

View File

@ -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
#

View File

@ -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 #

View File

@ -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();
}

View File

@ -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;

View File

@ -2598,6 +2598,8 @@ err:
bool st_select_lex_unit::cleanup()
{
cleanup_stranded_units();
bool error= 0;
DBUG_ENTER("st_select_lex_unit::cleanup");