Skip to content

Commit b639829

Browse files
committed
MDEV-38861 heap-use-after-free in Prepared_statement::execute()
DROP PROCEDURE and CREATE OR REPLACE PROCEDURE executed from inside the routine itself removed it from the SP cache. sp_head::destroy() then freed the memory root that the running sp_head, its LEX and its instructions live in, and the caller kept using them. Skip the removal while the routine is being executed. sp_cache_invalidate() above has already bumped the cache version, so the stale entry is removed by the next lookup, after IS_INVOKED has been cleared.
1 parent 19719d6 commit b639829

5 files changed

Lines changed: 218 additions & 1 deletion

File tree

‎mysql-test/main/sp-bugs.result‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,69 @@ DROP FUNCTION f;
400400
# End of 10.5 tests
401401
#
402402
#
403+
# MDEV-38861 heap-use-after-free in Prepared_statement::execute()
404+
#
405+
# A routine must not be removed from the SP cache while it is executing.
406+
#
407+
# DROP of the running routine
408+
create procedure p0()
409+
begin
410+
prepare stmt from "drop procedure p0";
411+
execute stmt;
412+
end$$
413+
call p0();
414+
call p0();
415+
ERROR 42000: PROCEDURE test.p0 does not exist
416+
deallocate prepare stmt;
417+
# CREATE OR REPLACE of the running routine
418+
create procedure p0()
419+
begin
420+
prepare stmt from "create or replace procedure p0() begin select 1 as replaced; end";
421+
execute stmt;
422+
end$$
423+
call p0();
424+
call p0();
425+
replaced
426+
1
427+
deallocate prepare stmt;
428+
drop procedure p0;
429+
# Recursive call after the self-DROP, with the default recursion depth
430+
set @save_depth=@@max_sp_recursion_depth;
431+
set @@max_sp_recursion_depth=0;
432+
create procedure p0()
433+
begin
434+
prepare stmt from "drop procedure p0";
435+
execute stmt;
436+
call p0();
437+
end$$
438+
call p0();
439+
ERROR HY000: Recursive limit 0 (as set by the max_sp_recursion_depth variable) was exceeded for routine p0
440+
call p0();
441+
ERROR 42000: PROCEDURE test.p0 does not exist
442+
deallocate prepare stmt;
443+
set @@max_sp_recursion_depth=@save_depth;
444+
# DROP of the caller, which then keeps running
445+
create procedure outer_p()
446+
begin
447+
call inner_p();
448+
select 'outer survived' as state;
449+
end$$
450+
create procedure inner_p()
451+
begin
452+
prepare stmt from "drop procedure outer_p";
453+
execute stmt;
454+
end$$
455+
call outer_p();
456+
state
457+
outer survived
458+
call outer_p();
459+
ERROR 42000: PROCEDURE test.outer_p does not exist
460+
deallocate prepare stmt;
461+
drop procedure inner_p;
462+
#
463+
# End of 11.4 tests
464+
#
465+
#
403466
# MDEV-36876 Crash during the Item_subselect::init - outer_select is NULL
404467
#
405468
# Vectors are not allowed for SOME/ANY

‎mysql-test/main/sp-bugs.test‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,79 @@ DROP FUNCTION f;
438438
--echo # End of 10.5 tests
439439
--echo #
440440

441+
--echo #
442+
--echo # MDEV-38861 heap-use-after-free in Prepared_statement::execute()
443+
--echo #
444+
--echo # A routine must not be removed from the SP cache while it is executing.
445+
--echo #
446+
447+
--echo # DROP of the running routine
448+
delimiter $$;
449+
create procedure p0()
450+
begin
451+
prepare stmt from "drop procedure p0";
452+
execute stmt;
453+
end$$
454+
delimiter ;$$
455+
call p0();
456+
--error ER_SP_DOES_NOT_EXIST
457+
call p0();
458+
deallocate prepare stmt;
459+
460+
--echo # CREATE OR REPLACE of the running routine
461+
delimiter $$;
462+
create procedure p0()
463+
begin
464+
prepare stmt from "create or replace procedure p0() begin select 1 as replaced; end";
465+
execute stmt;
466+
end$$
467+
delimiter ;$$
468+
call p0();
469+
call p0();
470+
deallocate prepare stmt;
471+
drop procedure p0;
472+
473+
--echo # Recursive call after the self-DROP, with the default recursion depth
474+
set @save_depth=@@max_sp_recursion_depth;
475+
set @@max_sp_recursion_depth=0;
476+
delimiter $$;
477+
create procedure p0()
478+
begin
479+
prepare stmt from "drop procedure p0";
480+
execute stmt;
481+
call p0();
482+
end$$
483+
delimiter ;$$
484+
--error ER_SP_RECURSION_LIMIT
485+
call p0();
486+
--error ER_SP_DOES_NOT_EXIST
487+
call p0();
488+
deallocate prepare stmt;
489+
set @@max_sp_recursion_depth=@save_depth;
490+
491+
--echo # DROP of the caller, which then keeps running
492+
delimiter $$;
493+
create procedure outer_p()
494+
begin
495+
call inner_p();
496+
select 'outer survived' as state;
497+
end$$
498+
create procedure inner_p()
499+
begin
500+
prepare stmt from "drop procedure outer_p";
501+
execute stmt;
502+
end$$
503+
delimiter ;$$
504+
call outer_p();
505+
--error ER_SP_DOES_NOT_EXIST
506+
call outer_p();
507+
deallocate prepare stmt;
508+
drop procedure inner_p;
509+
510+
--echo #
511+
--echo # End of 11.4 tests
512+
--echo #
513+
441514
--echo #
442515
--echo # MDEV-36876 Crash during the Item_subselect::init - outer_select is NULL
443516
--echo #

‎mysql-test/suite/compat/oracle/r/sp-package.result‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3381,3 +3381,42 @@ DROP TABLE t1;
33813381
DROP FUNCTION f1_deterministic;
33823382
DROP FUNCTION f2_not_deterministic;
33833383
DROP PACKAGE pkg1;
3384+
#
3385+
# MDEV-38861 heap-use-after-free in Prepared_statement::execute()
3386+
#
3387+
SET sql_mode=ORACLE;
3388+
# DROP PACKAGE BODY of the running package
3389+
CREATE PACKAGE pkg AS
3390+
PROCEDURE p1;
3391+
END$$
3392+
CREATE PACKAGE BODY pkg AS
3393+
PROCEDURE p1 AS
3394+
BEGIN
3395+
EXECUTE IMMEDIATE 'DROP PACKAGE BODY pkg';
3396+
SELECT 'body survived' AS state;
3397+
END;
3398+
END$$
3399+
CALL pkg.p1();
3400+
state
3401+
body survived
3402+
CALL pkg.p1();
3403+
ERROR 42000: PROCEDURE test.pkg.p1 does not exist
3404+
DROP PACKAGE pkg;
3405+
# CREATE OR REPLACE PACKAGE BODY of the running package
3406+
CREATE PACKAGE pkg AS
3407+
PROCEDURE p1;
3408+
END$$
3409+
CREATE PACKAGE BODY pkg AS
3410+
PROCEDURE p1 AS
3411+
BEGIN
3412+
EXECUTE IMMEDIATE 'CREATE OR REPLACE PACKAGE BODY pkg AS PROCEDURE p1 AS BEGIN SELECT ''replaced'' AS state; END; END';
3413+
SELECT 'body survived' AS state;
3414+
END;
3415+
END$$
3416+
CALL pkg.p1();
3417+
state
3418+
body survived
3419+
CALL pkg.p1();
3420+
state
3421+
replaced
3422+
DROP PACKAGE pkg;

‎mysql-test/suite/compat/oracle/t/sp-package.test‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3109,3 +3109,44 @@ DROP TABLE t1;
31093109
DROP FUNCTION f1_deterministic;
31103110
DROP FUNCTION f2_not_deterministic;
31113111
DROP PACKAGE pkg1;
3112+
3113+
--echo #
3114+
--echo # MDEV-38861 heap-use-after-free in Prepared_statement::execute()
3115+
--echo #
3116+
3117+
SET sql_mode=ORACLE;
3118+
3119+
--echo # DROP PACKAGE BODY of the running package
3120+
DELIMITER $$;
3121+
CREATE PACKAGE pkg AS
3122+
PROCEDURE p1;
3123+
END$$
3124+
CREATE PACKAGE BODY pkg AS
3125+
PROCEDURE p1 AS
3126+
BEGIN
3127+
EXECUTE IMMEDIATE 'DROP PACKAGE BODY pkg';
3128+
SELECT 'body survived' AS state;
3129+
END;
3130+
END$$
3131+
DELIMITER ;$$
3132+
CALL pkg.p1();
3133+
--error ER_SP_DOES_NOT_EXIST
3134+
CALL pkg.p1();
3135+
DROP PACKAGE pkg;
3136+
3137+
--echo # CREATE OR REPLACE PACKAGE BODY of the running package
3138+
DELIMITER $$;
3139+
CREATE PACKAGE pkg AS
3140+
PROCEDURE p1;
3141+
END$$
3142+
CREATE PACKAGE BODY pkg AS
3143+
PROCEDURE p1 AS
3144+
BEGIN
3145+
EXECUTE IMMEDIATE 'CREATE OR REPLACE PACKAGE BODY pkg AS PROCEDURE p1 AS BEGIN SELECT ''replaced'' AS state; END; END';
3146+
SELECT 'body survived' AS state;
3147+
END;
3148+
END$$
3149+
DELIMITER ;$$
3150+
CALL pkg.p1();
3151+
CALL pkg.p1();
3152+
DROP PACKAGE pkg;

‎sql/sp.cc‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1148,7 +1148,8 @@ Sp_handler::sp_drop_routine_internal(THD *thd,
11481148
sp_head *sp;
11491149
sp_cache **spc= get_cache(thd);
11501150
DBUG_ASSERT(spc);
1151-
if ((sp= sp_cache_lookup(spc, name)))
1151+
/* Removing a running routine would free the LEX being executed */
1152+
if ((sp= sp_cache_lookup(spc, name)) && !sp->is_invoked())
11521153
sp_cache_remove(spc, &sp);
11531154
/* Drop statistics for this stored program from performance schema. */
11541155
MYSQL_DROP_SP(type(), name->m_db.str, static_cast<uint>(name->m_db.length),

0 commit comments

Comments
 (0)