From 8994042941da4f4bdb15837cf840ef0bebd2ee0d Mon Sep 17 00:00:00 2001 From: Khaled Riyad Date: Fri, 4 Sep 2026 15:47:21 +0300 Subject: [PATCH] MDEV-38004 Double free on re-execution of prepared aggregate function Between two executions of a prepared statement, Item_sp::cleanup() freed the stored function's memory root but left the arena's free list pointing into it. That list is filled when the function call returns and the active arena is restored. On the next execution Item_sum_sp::clear() walked the stale list and destroyed already freed items. Free the items before freeing the memory they live in, the order Item_sum_sp::clear() already uses. --- .../main/custom_aggregate_functions.result | 49 +++++++++++++++++++ .../main/custom_aggregate_functions.test | 40 +++++++++++++++ sql/item.cc | 1 + 3 files changed, 90 insertions(+) diff --git a/mysql-test/main/custom_aggregate_functions.result b/mysql-test/main/custom_aggregate_functions.result index 7f2cde1b6fe88..dfcbf0533c6e8 100644 --- a/mysql-test/main/custom_aggregate_functions.result +++ b/mysql-test/main/custom_aggregate_functions.result @@ -1222,3 +1222,52 @@ ENDS CURRENT_TIMESTAMP + INTERVAL 1 MONTH + INTERVAL 1 WEEK DO FETCH GROUP NEXT ROW; ERROR HY000: Aggregate specific instruction (FETCH GROUP NEXT ROW) used in a wrong context # End of 10.4 tests +# +# MDEV-38004 Double free on repeated execution of prepared aggregate +# function against empty table in Query_arena::free_items() +# +create aggregate function agg_cnt() returns int +begin +declare z int default 0; +declare continue handler for not found return z; +loop +fetch group next row; +set z= z+1; +end loop; +end| +create table t1 (id int); +prepare stmt from "select agg_cnt() from t1"; +execute stmt; +agg_cnt() +NULL +execute stmt; +agg_cnt() +NULL +insert into t1 values (1),(2),(2); +execute stmt; +agg_cnt() +3 +execute stmt; +agg_cnt() +3 +deallocate prepare stmt; +# +# Compare with +select agg_cnt() from t1; +agg_cnt() +3 +prepare stmt from "select id, agg_cnt() from t1 group by id with rollup"; +execute stmt; +id agg_cnt() +1 1 +2 2 +NULL 3 +execute stmt; +id agg_cnt() +1 1 +2 2 +NULL 3 +deallocate prepare stmt; +drop function agg_cnt; +drop table t1; +# End of 10.11 tests diff --git a/mysql-test/main/custom_aggregate_functions.test b/mysql-test/main/custom_aggregate_functions.test index a6097b3784791..e7cb675f4b92f 100644 --- a/mysql-test/main/custom_aggregate_functions.test +++ b/mysql-test/main/custom_aggregate_functions.test @@ -1062,3 +1062,43 @@ CREATE EVENT ev1 DO FETCH GROUP NEXT ROW; --echo # End of 10.4 tests + +--echo # +--echo # MDEV-38004 Double free on repeated execution of prepared aggregate +--echo # function against empty table in Query_arena::free_items() +--echo # + +--delimiter | +create aggregate function agg_cnt() returns int +begin +declare z int default 0; +declare continue handler for not found return z; +loop +fetch group next row; +set z= z+1; +end loop; +end| +--delimiter ; + +create table t1 (id int); +prepare stmt from "select agg_cnt() from t1"; +execute stmt; +execute stmt; +insert into t1 values (1),(2),(2); +execute stmt; +execute stmt; +deallocate prepare stmt; +--echo # +--echo # Compare with +select agg_cnt() from t1; + +prepare stmt from "select id, agg_cnt() from t1 group by id with rollup"; +execute stmt; +execute stmt; +deallocate prepare stmt; + +# Cleanup +drop function agg_cnt; +drop table t1; + +--echo # End of 10.11 tests diff --git a/sql/item.cc b/sql/item.cc index b3fbf6b72e876..d77b195570565 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -2880,6 +2880,7 @@ Item_sp::cleanup() m_sp= NULL; delete func_ctx; func_ctx= NULL; + sp_query_arena->free_items(); free_root(&sp_mem_root, MYF(0)); dummy_table->alias.free(); }