Skip to content

Commit 34a8209

Browse files
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.
1 parent aec79c5 commit 34a8209

File tree

5 files changed

+118
-0
lines changed

5 files changed

+118
-0
lines changed

mysql-test/main/merge.result

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3930,3 +3930,43 @@ drop table tm, t;
39303930
#
39313931
# End of 10.8 tests
39323932
#
3933+
#
3934+
# MDEV-35816 ASAN use-after-poison in st_select_lex::print
3935+
#
3936+
CREATE TABLE t1 (a INT);
3937+
INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5);
3938+
SET SESSION optimizer_trace = 'enabled=on';
3939+
PREPARE stmt FROM 'SELECT STRAIGHT_JOIN * FROM t1 WHERE a IN (WITH cte AS (SELECT a FROM t1) SELECT * FROM cte)';
3940+
EXECUTE stmt;
3941+
a
3942+
1
3943+
2
3944+
3
3945+
4
3946+
5
3947+
EXECUTE stmt;
3948+
a
3949+
1
3950+
2
3951+
3
3952+
4
3953+
5
3954+
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)';
3955+
EXECUTE nested;
3956+
a
3957+
1
3958+
2
3959+
3
3960+
4
3961+
5
3962+
EXECUTE nested;
3963+
a
3964+
1
3965+
2
3966+
3
3967+
4
3968+
5
3969+
DROP TABLE t1;
3970+
#
3971+
# End of 10.11 tests
3972+
#

mysql-test/main/merge.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2887,3 +2887,21 @@ drop table tm, t;
28872887
--echo #
28882888
--echo # End of 10.8 tests
28892889
--echo #
2890+
2891+
--echo #
2892+
--echo # MDEV-35816 ASAN use-after-poison in st_select_lex::print
2893+
--echo #
2894+
CREATE TABLE t1 (a INT);
2895+
INSERT INTO t1 (a) VALUES (1),(2),(3),(4),(5);
2896+
SET SESSION optimizer_trace = 'enabled=on';
2897+
PREPARE stmt FROM 'SELECT STRAIGHT_JOIN * FROM t1 WHERE a IN (WITH cte AS (SELECT a FROM t1) SELECT * FROM cte)';
2898+
EXECUTE stmt;
2899+
EXECUTE stmt;
2900+
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)';
2901+
EXECUTE nested;
2902+
EXECUTE nested;
2903+
DROP TABLE t1;
2904+
2905+
--echo #
2906+
--echo # End of 10.11 tests
2907+
--echo #

sql/sql_lex.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2959,6 +2959,41 @@ void st_select_lex_node::init_query_common()
29592959
uncacheable= 0;
29602960
}
29612961

2962+
2963+
/*
2964+
We need to remember this unit for cleanup after it is stranded during CTE
2965+
merge (see mysql_derived_merge). Walk to the root unit of this query tree
2966+
(the root unit lifetime extends for the entire query) and insert myself
2967+
into the front of the stranded_clean_list:
2968+
before: root -> B -> A
2969+
after: root -> this -> B -> A
2970+
During cleanup, the stranded units are cleaned in FIFO order.
2971+
*/
2972+
void st_select_lex_unit::remember_my_cleanup()
2973+
{
2974+
// Walk to the root unit (which lives until the end of the query) ...
2975+
st_select_lex_node *root= this;
2976+
while (root->master)
2977+
root= root->master;
2978+
2979+
// ... and add myself to the front of the stranded_clean_list.
2980+
st_select_lex_unit *unit= static_cast<st_select_lex_unit*>(root);
2981+
st_select_lex_unit *prior_head= unit->stranded_clean_list;
2982+
unit->stranded_clean_list= this;
2983+
stranded_clean_list= prior_head;
2984+
}
2985+
2986+
2987+
void st_select_lex_unit::cleanup_stranded_units()
2988+
{
2989+
if (!stranded_clean_list)
2990+
return;
2991+
2992+
stranded_clean_list->cleanup();
2993+
stranded_clean_list= nullptr;
2994+
}
2995+
2996+
29622997
void st_select_lex_unit::init_query()
29632998
{
29642999
init_query_common();
@@ -3384,6 +3419,7 @@ void st_select_lex_unit::exclude_level()
33843419
}
33853420
// Mark it excluded
33863421
prev= NULL;
3422+
remember_my_cleanup();
33873423
}
33883424

33893425

sql/sql_lex.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,28 @@ bool print_explain_for_slow_log(LEX *lex, THD *thd, String *str);
862862

863863

864864
class st_select_lex_unit: public st_select_lex_node {
865+
private:
866+
/*
867+
When a CTE is merged to the parent SELECT, its unit is excluded
868+
which separates it from the tree of units for this query. It
869+
needs to be cleaned up but not at the time it is excluded, since
870+
its queries are merged to the unit above it. Remember all such
871+
units via the stranded_clean_list and clean them at the end of
872+
the query. This list is maintained only at the root unit node
873+
of the query tree.
874+
*/
875+
st_select_lex_unit *stranded_clean_list{nullptr};
876+
877+
// Add myself to the stranded_clean_list.
878+
void remember_my_cleanup();
879+
880+
/*
881+
Walk the stranded_clean_list and cleanup units. This must only
882+
be called for the st_select_lex_unit type because it assumes
883+
that those are the only nodes in the stranded_clean_list.
884+
*/
885+
void cleanup_stranded_units();
886+
865887
protected:
866888
TABLE_LIST result_table_list;
867889
select_unit *union_result;

sql/sql_union.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2598,6 +2598,8 @@ bool st_select_lex_unit::exec_recursive()
25982598

25992599
bool st_select_lex_unit::cleanup()
26002600
{
2601+
cleanup_stranded_units();
2602+
26012603
bool error= 0;
26022604
DBUG_ENTER("st_select_lex_unit::cleanup");
26032605

0 commit comments

Comments
 (0)