From 1cc67e090ebca36f04fdd6fa91e27a9c10d764d2 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Fri, 18 May 2018 19:12:35 +0200 Subject: [PATCH] MDEV-16153 Server crashes in Apc_target::disable, ASAN heap-use-after-free in Explain_query::~Explain_query upon/after EXECUTE IMMEDIATE Explain_query must be created in the execution arena. But JOIN::optimize_inner temporarily switches to the statement arena under `if (sel->first_cond_optimization)`. This might cause Explain_query to be allocated in the statement arena. Usually it is harmless (although technically incorrect and a waste of memory), but in case of EXECUTE IMMEDIATE, Prepared_statement object and its statement arena are destroyed before log_slow_statement() call, which uses Explain_query. Fix: 1. Create Explain_query before switching arenas. 2. Before filling earlier-created Explain_query with data, set thd->mem_root from the Explain_query::mem_root --- mysql-test/r/explain_slowquerylog.result | 4 ++++ mysql-test/t/explain_slowquerylog.test | 6 ++++++ sql/sql_select.cc | 17 ++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/explain_slowquerylog.result b/mysql-test/r/explain_slowquerylog.result index 2b350cf04ff..63da82b5288 100644 --- a/mysql-test/r/explain_slowquerylog.result +++ b/mysql-test/r/explain_slowquerylog.result @@ -54,3 +54,7 @@ SELECT 1; 1 SET log_slow_rate_limit=@save1; SET long_query_time=@save2; +create table t1 (a int); +execute immediate "select * from t1 join t1 t2 on (t1.a>5) where exists (select 1)"; +a a +drop table t1; diff --git a/mysql-test/t/explain_slowquerylog.test b/mysql-test/t/explain_slowquerylog.test index 6503a326eb8..ee90fbac4e6 100644 --- a/mysql-test/t/explain_slowquerylog.test +++ b/mysql-test/t/explain_slowquerylog.test @@ -61,3 +61,9 @@ SELECT 1; SET log_slow_rate_limit=@save1; SET long_query_time=@save2; +# +# MDEV-16153 Server crashes in Apc_target::disable, ASAN heap-use-after-free in Explain_query::~Explain_query upon/after EXECUTE IMMEDIATE +# +create table t1 (a int); +execute immediate "select * from t1 join t1 t2 on (t1.a>5) where exists (select 1)"; +drop table t1; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 1d5abd6a36f..abbfd1f6ea9 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1117,10 +1117,21 @@ int JOIN::optimize() { create_explain_query_if_not_exists(thd->lex, thd->mem_root); have_query_plan= QEP_AVAILABLE; + + /* + explain data must be created on the Explain_query::mem_root. Because it's + just a memroot, not an arena, explain data must not contain any Items + */ + MEM_ROOT *old_mem_root= thd->mem_root; + Item *old_free_list __attribute__((unused))= thd->free_list; + thd->mem_root= thd->lex->explain->mem_root; save_explain_data(thd->lex->explain, false /* can overwrite */, need_tmp, !skip_sort_order && !no_order && (order || group_list), select_distinct); + thd->mem_root= old_mem_root; + DBUG_ASSERT(thd->free_list == old_free_list); // no Items were created + uint select_nr= select_lex->select_number; JOIN_TAB *curr_tab= join_tab + exec_join_tab_cnt(); for (uint i= 0; i < aggr_tables; i++, curr_tab++) @@ -1294,7 +1305,11 @@ JOIN::optimize_inner() /* The following code will allocate the new items in a permanent MEMROOT for prepared statements and stored procedures. + + But first we need to ensure that thd->lex->explain is allocated + in the execution arena */ + create_explain_query_if_not_exists(thd->lex, thd->mem_root); Query_arena *arena, backup; arena= thd->activate_stmt_arena_if_needed(&backup); @@ -24330,7 +24345,7 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, if (filesort) { - eta->pre_join_sort= new Explain_aggr_filesort(thd->mem_root, + eta->pre_join_sort= new (thd->mem_root) Explain_aggr_filesort(thd->mem_root, thd->lex->analyze_stmt, filesort); }