Skip to content

Commit 8aaacb5

Browse files
committed
MDEV-31432 tmp_table field accessed after free
Before this patch, the code in Item_field::print() used this convention (described in sql_explain.h:ExplainDataStructureLifetime): - By default, the table that Item_field refers to is accessible. - ANALYZE and SHOW {EXPLAIN|ANALYZE} may print Items after some temporary tables have been dropped. They use QT_DONT_ACCESS_TMP_TABLES flag. When it is ON, Item_field::print will not access the table it refers to, if it is a temp.table The bug was that EXPLAIN statement also may compute subqueries (depending on subquery context and @@expensive_subquery_limit setting). After the computation, the subquery calls JOIN::cleanup(true) which drops some of its temporary tables. Calling Item_field::print() that refer to such table will cause an access to free'd memory. In this patch, we take into account that query optimization can compute a subquery and discard its temporary tables. Item_field::print() now assumes that any temporary table might have already been dropped. This means QT_DONT_ACCESS_TMP_TABLES flag is not needed - we imply it is always present. But we also make one exception: derived tables are not freed in JOIN::cleanup() call. They are freed later in close_thread_tables(), at the same time when regular tables are closed. Because of that, Item_field::print may assume that temp.tables representing derived tables are available. Initial patch by: Rex Jonston Reviewed by: Monty <monty@mariadb.org>
1 parent 9cd2989 commit 8aaacb5

15 files changed

+199
-157
lines changed

mysql-test/main/show_analyze.result

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,3 +434,42 @@ ANALYZE
434434
}
435435
}
436436
DROP TABLE t1;
437+
#
438+
# MDEV-31432 tmp_table field accessed after free
439+
# testing for the above (MDEV-28201) caused use after free error
440+
#
441+
create table t1 (x int) engine=myisam;
442+
insert into t1 values(1);
443+
set @tmp=@@optimizer_trace;
444+
set @@optimizer_trace=1;
445+
SELECT
446+
1 IN
447+
((
448+
SELECT
449+
1 IN (SELECT 1 AS x0
450+
FROM
451+
(
452+
SELECT *
453+
FROM (SELECT 1 AS x) AS x5
454+
GROUP BY x,x
455+
HAVING
456+
x IN (
457+
SELECT *
458+
FROM t1 AS x1
459+
WHERE
460+
x IN (SELECT 1 AS x
461+
FROM t1 AS x3
462+
GROUP BY x
463+
HAVING
464+
x IN (SELECT 0 FROM t1 AS x4)
465+
)
466+
)
467+
) AS x6
468+
)
469+
FROM
470+
t1
471+
)) as VAL;
472+
VAL
473+
0
474+
set optimizer_trace=@tmp;
475+
drop table t1;

mysql-test/main/show_analyze.test

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,3 +364,44 @@ ANALYZE format=json
364364
SELECT 1 FROM t1 GROUP BY convert_tz('1969-12-31 22:00:00',a,'+10:00');
365365
DROP TABLE t1;
366366

367+
--echo #
368+
--echo # MDEV-31432 tmp_table field accessed after free
369+
--echo # testing for the above (MDEV-28201) caused use after free error
370+
--echo #
371+
create table t1 (x int) engine=myisam;
372+
insert into t1 values(1);
373+
set @tmp=@@optimizer_trace;
374+
set @@optimizer_trace=1;
375+
# Different warning text is produced in regular and --ps-protocol runs:
376+
--disable_warnings
377+
SELECT
378+
1 IN
379+
((
380+
SELECT
381+
1 IN (SELECT 1 AS x0
382+
FROM
383+
(
384+
SELECT *
385+
FROM (SELECT 1 AS x) AS x5
386+
GROUP BY x,x
387+
HAVING
388+
x IN (
389+
SELECT *
390+
FROM t1 AS x1
391+
WHERE
392+
x IN (SELECT 1 AS x
393+
FROM t1 AS x3
394+
GROUP BY x
395+
HAVING
396+
x IN (SELECT 0 FROM t1 AS x4)
397+
)
398+
)
399+
) AS x6
400+
)
401+
FROM
402+
t1
403+
)) as VAL;
404+
--enable_warnings
405+
set optimizer_trace=@tmp;
406+
drop table t1;
407+

sql/item.cc

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3138,7 +3138,7 @@ void Item_field::set_field(Field *field_par)
31383138

31393139
if (field->table->s->tmp_table == SYSTEM_TMP_TABLE ||
31403140
field->table->s->tmp_table == INTERNAL_TMP_TABLE)
3141-
set_refers_to_temp_table(true);
3141+
set_refers_to_temp_table();
31423142
}
31433143

31443144

@@ -3615,7 +3615,7 @@ Item *Item_field::get_tmp_table_item(THD *thd)
36153615
if (new_item)
36163616
{
36173617
new_item->field= new_item->result_field;
3618-
new_item->set_refers_to_temp_table(true);
3618+
new_item->set_refers_to_temp_table();
36193619
}
36203620
return new_item;
36213621
}
@@ -3626,9 +3626,14 @@ longlong Item_field::val_int_endpoint(bool left_endp, bool *incl_endp)
36263626
return null_value? LONGLONG_MIN : res;
36273627
}
36283628

3629-
void Item_field::set_refers_to_temp_table(bool value)
3629+
void Item_field::set_refers_to_temp_table()
36303630
{
3631-
refers_to_temp_table= value;
3631+
/*
3632+
Derived temp. tables have non-zero derived_select_number.
3633+
We don't need to distingish between other kinds of temp.tables currently.
3634+
*/
3635+
refers_to_temp_table= (field->table->derived_select_number != 0)?
3636+
REFERS_TO_DERIVED_TMP : REFERS_TO_OTHER_TMP;
36323637
}
36333638

36343639

@@ -6292,7 +6297,7 @@ void Item_field::cleanup()
62926297
field= 0;
62936298
item_equal= NULL;
62946299
null_value= FALSE;
6295-
refers_to_temp_table= FALSE;
6300+
refers_to_temp_table= NO_TEMP_TABLE;
62966301
DBUG_VOID_RETURN;
62976302
}
62986303

@@ -7860,14 +7865,15 @@ void Item_field::print(String *str, enum_query_type query_type)
78607865
{
78617866
/*
78627867
If the field refers to a constant table, print the value.
7863-
(1): But don't attempt to do that if
7864-
* the field refers to a temporary (work) table, and
7865-
* temp. tables might already have been dropped.
7868+
There are two exceptions:
7869+
1. For temporary (aka "work") tables, we can only access the derived temp.
7870+
tables. Other kinds of tables might already have been dropped.
7871+
2. Don't print constants if QT_NO_DATA_EXPANSION or QT_VIEW_INTERNAL is
7872+
specified.
78667873
*/
7867-
if (!(refers_to_temp_table && // (1)
7868-
(query_type & QT_DONT_ACCESS_TMP_TABLES)) && // (1)
7869-
field && field->table->const_table &&
7870-
!(query_type & (QT_NO_DATA_EXPANSION | QT_VIEW_INTERNAL)))
7874+
if ((refers_to_temp_table != REFERS_TO_OTHER_TMP) && // (1)
7875+
!(query_type & (QT_NO_DATA_EXPANSION | QT_VIEW_INTERNAL)) && // (2)
7876+
field && field->table->const_table)
78717877
{
78727878
print_value(str);
78737879
return;
@@ -9145,7 +9151,7 @@ Item* Item_cache_wrapper::get_tmp_table_item(THD *thd)
91459151
{
91469152
auto item_field= new (thd->mem_root) Item_field(thd, result_field);
91479153
if (item_field)
9148-
item_field->set_refers_to_temp_table(true);
9154+
item_field->set_refers_to_temp_table();
91499155
return item_field;
91509156
}
91519157
return copy_or_same(thd);

sql/item.h

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3568,27 +3568,18 @@ class Item_field :public Item_ident,
35683568

35693569
private:
35703570
/*
3571-
Setting this member to TRUE (via set_refers_to_temp_table())
3572-
ensures print() function continues to work even if the table
3573-
has been dropped.
3571+
Indicates whether this Item_field refers to a regular or some kind of
3572+
temporary table.
3573+
This is needed for print() to work: it may be called even after the table
3574+
referred by the Item_field has been dropped.
35743575
3575-
We need this for "ANALYZE statement" feature. Query execution has
3576-
these steps:
3577-
1. Run the query.
3578-
2. Cleanup starts. Temporary tables are destroyed
3579-
3. print "ANALYZE statement" output, if needed
3580-
4. Call close_thread_table() for regular tables.
3581-
3582-
Step #4 is done after step #3, so "ANALYZE stmt" has no problem printing
3583-
Item_field objects that refer to regular tables.
3584-
3585-
However, Step #3 is done after Step #2. Attempt to print Item_field objects
3586-
that refer to temporary tables will cause access to freed memory.
3587-
3588-
To resolve this, we use refers_to_temp_table member to refer to items
3589-
in temporary (work) tables.
3576+
See ExplainDataStructureLifetime in sql_explain.h for details.
35903577
*/
3591-
bool refers_to_temp_table= false;
3578+
enum {
3579+
NO_TEMP_TABLE= 0,
3580+
REFERS_TO_DERIVED_TMP= 1,
3581+
REFERS_TO_OTHER_TMP=2
3582+
} refers_to_temp_table = NO_TEMP_TABLE;
35923583

35933584
public:
35943585
Item_field(THD *thd, Name_resolution_context *context_arg,
@@ -3804,7 +3795,7 @@ class Item_field :public Item_ident,
38043795
return field->table->pos_in_table_list->outer_join;
38053796
}
38063797
bool check_index_dependence(void *arg) override;
3807-
void set_refers_to_temp_table(bool value);
3798+
void set_refers_to_temp_table();
38083799
friend class Item_default_value;
38093800
friend class Item_insert_value;
38103801
friend class st_select_lex_unit;

sql/item_func.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ Item *Item_func::get_tmp_table_item(THD *thd)
749749
{
750750
auto item_field= new (thd->mem_root) Item_field(thd, result_field);
751751
if (item_field)
752-
item_field->set_refers_to_temp_table(true);
752+
item_field->set_refers_to_temp_table();
753753
return item_field;
754754
}
755755
return copy_or_same(thd);

sql/item_subselect.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,7 @@ Item *Item_subselect::get_tmp_table_item(THD *thd_arg)
10301030
auto item_field=
10311031
new (thd->mem_root) Item_field(thd_arg, result_field);
10321032
if (item_field)
1033-
item_field->set_refers_to_temp_table(true);
1033+
item_field->set_refers_to_temp_table();
10341034
return item_field;
10351035
}
10361036
return copy_or_same(thd_arg);
@@ -5310,7 +5310,7 @@ bool subselect_hash_sj_engine::make_semi_join_conds()
53105310
Item_field *right_col_item= new (thd->mem_root)
53115311
Item_field(thd, context, tmp_table->field[i]);
53125312
if (right_col_item)
5313-
right_col_item->set_refers_to_temp_table(true);
5313+
right_col_item->set_refers_to_temp_table();
53145314

53155315
if (!right_col_item ||
53165316
!(eq_cond= new (thd->mem_root)

sql/item_sum.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ Item *Item_sum::get_tmp_table_item(THD *thd)
562562
auto item_field=
563563
new (thd->mem_root) Item_field(thd, result_field_tmp++);
564564
if (item_field)
565-
item_field->set_refers_to_temp_table(true);
565+
item_field->set_refers_to_temp_table();
566566
sum_item->args[i]= item_field;
567567
}
568568
}

sql/mysqld.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -905,11 +905,7 @@ enum enum_query_type
905905
// don't reveal values.
906906
QT_NO_DATA_EXPANSION= (1 << 9),
907907
// Remove wrappers added for TVC when creating or showing view
908-
QT_NO_WRAPPERS_FOR_TVC_IN_VIEW= (1 << 12),
909-
910-
// The temporary tables used by the query might be freed by the time
911-
// this print() call is made.
912-
QT_DONT_ACCESS_TMP_TABLES= (1 << 13)
908+
QT_NO_WRAPPERS_FOR_TVC_IN_VIEW= (1 << 12)
913909
};
914910

915911

sql/sql_base.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,9 @@ int close_thread_tables(THD *thd)
871871
TODO: Probably even better approach is to simply associate list of
872872
derived tables with (sub-)statement instead of thread and destroy
873873
them at the end of its execution.
874+
875+
Note: EXPLAIN/ANALYZE depends on derived tables being freed here. See
876+
sql_explain.h:ExplainDataStructureLifetime.
874877
*/
875878
if (thd->derived_tables)
876879
{

0 commit comments

Comments
 (0)