Skip to content

Commit 852e451

Browse files
committed
MDEV-26115: Crash when calling stored function in FOR loop argument
On handling SP statement `FOR IN lower_bound..func() DO` the instruction sp_instr_set is allocated on sp_head's memory root, whereas an instance of the class Item_func_sp pointed by the data member sp_instr_set::sp_result_field is allocated on runtime memory root. In result, on finishing the first execution of a stored routine the memory allocated for the instance of the class Item_func_sp is released whereas the pointer sp_instr_set::sp_result_field still references the deleted memory. Next time the same stored routine is run dereferencing deallocated memory results in abnormal server termination. To fix the issue, allocate an instance of the class Item_func_sp on sp_head memory root. Do this allocation only once, meaning the Item_func_sp::cleanup doesn't do deletion an instance of the class Item_func_sp and nullifying the data member sp_instr_set::sp_result_field.
1 parent 288db5f commit 852e451

File tree

6 files changed

+114
-19
lines changed

6 files changed

+114
-19
lines changed

mysql-test/main/sp-bugs2.result

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,39 @@ call p2('2');
5959
drop table t1;
6060
drop procedure p1;
6161
drop procedure p2;
62+
#
63+
# MDEV-26115: Crash when calling stored function in FOR loop argument
64+
#
65+
CREATE OR REPLACE FUNCTION cnt()
66+
RETURNS INTEGER NO SQL
67+
BEGIN
68+
RETURN 3;
69+
END;
70+
$
71+
CREATE OR REPLACE PROCEDURE p1()
72+
NO SQL
73+
BEGIN
74+
DECLARE i INTEGER;
75+
FOR i IN 1..cnt() DO
76+
SELECT 1;
77+
END FOR;
78+
END;
79+
$
80+
CALL p1();
81+
1
82+
1
83+
1
84+
1
85+
1
86+
1
87+
CALL p1();
88+
1
89+
1
90+
1
91+
1
92+
1
93+
1
94+
# Clean up
95+
DROP FUNCTION cnt;
96+
DROP PROCEDURE p1;
6297
# End of 10.11 tests

mysql-test/main/sp-bugs2.test

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,34 @@ drop table t1;
6767
drop procedure p1;
6868
drop procedure p2;
6969

70+
--echo #
71+
--echo # MDEV-26115: Crash when calling stored function in FOR loop argument
72+
--echo #
73+
--delimiter $
74+
CREATE OR REPLACE FUNCTION cnt()
75+
RETURNS INTEGER NO SQL
76+
BEGIN
77+
RETURN 3;
78+
END;
79+
$
80+
81+
CREATE OR REPLACE PROCEDURE p1()
82+
NO SQL
83+
BEGIN
84+
DECLARE i INTEGER;
85+
FOR i IN 1..cnt() DO
86+
SELECT 1;
87+
END FOR;
88+
END;
89+
$
90+
91+
--delimiter ;
92+
93+
CALL p1();
94+
CALL p1();
95+
96+
--echo # Clean up
97+
DROP FUNCTION cnt;
98+
DROP PROCEDURE p1;
99+
70100
--echo # End of 10.11 tests

sql/item.cc

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2898,8 +2898,6 @@ Item_sp::func_name_cstring(THD *thd, bool is_package_function) const
28982898
void
28992899
Item_sp::cleanup()
29002900
{
2901-
delete sp_result_field;
2902-
sp_result_field= NULL;
29032901
m_sp= NULL;
29042902
delete func_ctx;
29052903
func_ctx= NULL;
@@ -3071,7 +3069,6 @@ Item_sp::init_result_field(THD *thd, uint max_length, uint maybe_null,
30713069
DBUG_ENTER("Item_sp::init_result_field");
30723070

30733071
DBUG_ASSERT(m_sp != NULL);
3074-
DBUG_ASSERT(sp_result_field == NULL);
30753072

30763073
/*
30773074
A Field needs to be attached to a Table.
@@ -3085,23 +3082,26 @@ Item_sp::init_result_field(THD *thd, uint max_length, uint maybe_null,
30853082
dummy_table->s->table_name= empty_clex_str;
30863083
dummy_table->maybe_null= maybe_null;
30873084

3088-
if (!(sp_result_field= m_sp->create_result_field(max_length, name,
3089-
dummy_table)))
3090-
DBUG_RETURN(TRUE);
3091-
3092-
if (sp_result_field->pack_length() > sizeof(result_buf))
3085+
if (!sp_result_field)
30933086
{
3094-
void *tmp;
3095-
if (!(tmp= thd->alloc(sp_result_field->pack_length())))
3096-
DBUG_RETURN(TRUE);
3097-
sp_result_field->move_field((uchar*) tmp);
3098-
}
3099-
else
3100-
sp_result_field->move_field(result_buf);
3087+
sp_result_field= m_sp->create_result_field(max_length, name,
3088+
dummy_table);
3089+
if (!sp_result_field)
3090+
DBUG_RETURN(true);
31013091

3102-
sp_result_field->null_ptr= (uchar *) null_value;
3103-
sp_result_field->null_bit= 1;
3092+
if (sp_result_field->pack_length() > sizeof(result_buf))
3093+
{
3094+
void *tmp;
3095+
if (!(tmp= thd->alloc(sp_result_field->pack_length())))
3096+
DBUG_RETURN(TRUE);
3097+
sp_result_field->move_field((uchar*) tmp);
3098+
}
3099+
else
3100+
sp_result_field->move_field(result_buf);
31043101

3102+
sp_result_field->null_ptr= (uchar *) null_value;
3103+
sp_result_field->null_bit= 1;
3104+
}
31053105
DBUG_RETURN(FALSE);
31063106
}
31073107

sql/item.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5815,6 +5815,11 @@ class Item_sp
58155815
Field *sp_result_field;
58165816
Item_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name_arg);
58175817
Item_sp(THD *thd, Item_sp *item);
5818+
virtual ~Item_sp()
5819+
{
5820+
delete sp_result_field;
5821+
sp_result_field= NULL;
5822+
}
58185823
LEX_CSTRING func_name_cstring(THD *thd, bool is_package_function) const;
58195824
void cleanup();
58205825
bool sp_check_access(THD *thd);

sql/item_func.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6837,12 +6837,29 @@ Item_func_sp::fix_fields(THD *thd, Item **ref)
68376837
DBUG_RETURN(TRUE);
68386838
}
68396839

6840+
Query_arena *arena, backup;
6841+
/*
6842+
Allocation an instance of Item_func_sp used for initialization of
6843+
sp_result_field taken place inside the method init_result_field() is done
6844+
on sp_head's mem_root since Item_sp also allocated on this memory root.
6845+
6846+
Switching to SP/PS memory root is done explicitly before calling the method
6847+
init_result_field() instead doing that inside init_result_field()
6848+
since for the case when rollup aggregate function is handled
6849+
(@see Item_sum_sp::copy_or_same, @see JOIN::rollup_make_fields)
6850+
the runtime arena used for operations, so switching to SP/PS arena for this
6851+
case would result in assertion failure on second execution of the same
6852+
prepared statement because the memory root be already marked as read only.
6853+
*/
6854+
arena= thd->activate_stmt_arena_if_needed(&backup);
68406855
/*
68416856
We must call init_result_field before Item_func::fix_fields()
68426857
to make m_sp and result_field members available to fix_length_and_dec(),
68436858
which is called from Item_func::fix_fields().
68446859
*/
68456860
res= init_result_field(thd, max_length, maybe_null(), &null_value, &name);
6861+
if (arena)
6862+
thd->restore_active_arena(arena, &backup);
68466863

68476864
if (res)
68486865
DBUG_RETURN(TRUE);

sql/item_sum.cc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,8 +1365,16 @@ Item_sum_sp::fix_fields(THD *thd, Item **ref)
13651365
return TRUE;
13661366
}
13671367

1368-
if (init_result_field(thd, max_length, maybe_null(), &null_value, &name))
1369-
return TRUE;
1368+
Query_arena *arena, backup;
1369+
arena= thd->activate_stmt_arena_if_needed(&backup);
1370+
1371+
bool ret= init_result_field(thd, max_length, maybe_null(),
1372+
&null_value, &name);
1373+
if (arena)
1374+
thd->restore_active_arena(arena, &backup);
1375+
1376+
if(ret)
1377+
return true;
13701378

13711379
for (uint i= 0 ; i < arg_count ; i++)
13721380
{

0 commit comments

Comments
 (0)