From bb3a52fabfed2cdba2e19aba25ba915aa93a4faa Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Mon, 5 Oct 2026 12:25:28 +0300 Subject: [PATCH] MDEV-29483 Test for wrong result with window function and IN subquery Add a test case to main.win that still returns a wrong result with the previous commit applied. The result file records the current (wrong) output: an empty set. The expected result is one row 'b'. CREATE TABLE t1 (c0 TEXT, c1 INT); INSERT INTO t1 VALUES ('a', 1), ('b', 2); CREATE TABLE t2 (c0 TEXT); INSERT INTO t2 VALUES ('b'); SELECT COALESCE(c0, LAST_VALUE(c0) OVER (ORDER BY c1)) FROM (SELECT c0, c1 FROM t1) dt WHERE c1 = 0 OR c0 IN (SELECT c0 FROM t2); Findings so far: 1. The derived table dt is merged. The outer references to c0 in the WHERE clause and in the select list become Item_direct_view_ref objects pointing to one shared Item_field. 2. The window function forces a post-join aggregation temporary table (JOIN::make_aggr_tables_info() -> create_tmp_table()). create_tmp_field() with modify_item redirects result_field of that shared Item_field to the temporary table field. 3. The WHERE clause is evaluated in evaluate_join_record() for each join row, before the row is written to the temporary table. The IN predicate caches its left expression: Item_in_optimizer::val_bool() -> Item_cache_str::cache_value() -> Item_direct_view_ref::str_result() -> Item_field::str_result(), which reads result_field, not field. 4. So the WHERE clause compares the value copied by copy_fields() for the last row that passed the WHERE clause, not the current row. In the test, row 'a' sees an empty temporary table field and is filtered out, so nothing is copied, and row 'b' sees the same empty field and is filtered out too. 5. The heap-use-after-free from the original report is the same bug with a BLOB column on InnoDB. copy_fields() copied the BLOB with do_field_eq(), which keeps a pointer into the InnoDB row blob heap. InnoDB frees that heap on the next row fetch (row_mysql_prebuilt_free_blob_heap()), and the next WHERE evaluation dereferences it in Binary_string::copy(). Recorded with rr: the only free of the crashing block happens in the t1 scan on the row1 -> row2 transition, and at the crash result_field belongs to the temporary table while field holds row2's own value. 6. The previous commit deep-copies BLOBs for window function queries, which removes the use-after-free but not the stale read. The wrong result is not BLOB-specific and does not depend on InnoDB. 7. With a plain c0 in the select list instead of the COALESCE() expression the result is correct, so the shape of the select list item affects whether result_field is redirected. The real fix belongs to the optimizer layer: either the merged derived table must not share an item between a WHERE condition and a select list item that gets a temporary table result_field, or the temporary table setup must not redirect such an item, or the IN predicate cache must read field when evaluated before the temporary table is filled. MDEV-39868 (commit c0c4b5573f9) is a similar case of merged derived table columns sharing one Item_field. --- mysql-test/main/win.result | 13 +++++++++++++ mysql-test/main/win.test | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/mysql-test/main/win.result b/mysql-test/main/win.result index 7891c737dfd..6bdee3214bc 100644 --- a/mysql-test/main/win.result +++ b/mysql-test/main/win.result @@ -4794,5 +4794,18 @@ c1 c2 2 2 DROP TABLE t1; # +# MDEV-29483 Heap-use-after-free (Binary_string::copy()) with window functions +# +create table t1 (c0 text, c1 int); +insert into t1 values ('a', 1), ('b', 2); +create table t2 (c0 text); +insert into t2 values ('b'); +# Expected: one row 'b' +select coalesce(c0, last_value(c0) over (order by c1)) from (select c0, c1 from t1) dt +where c1 = 0 or c0 in (select c0 from t2); +coalesce(c0, last_value(c0) over (order by c1)) +b +drop table t1, t2; +# # End of 10.11 tests # diff --git a/mysql-test/main/win.test b/mysql-test/main/win.test index 35f59041d3b..04c9270778c 100644 --- a/mysql-test/main/win.test +++ b/mysql-test/main/win.test @@ -3108,6 +3108,20 @@ from t1; DROP TABLE t1; +--echo # +--echo # MDEV-29483 Heap-use-after-free (Binary_string::copy()) with window functions +--echo # +create table t1 (c0 text, c1 int); +insert into t1 values ('a', 1), ('b', 2); +create table t2 (c0 text); +insert into t2 values ('b'); + +--echo # Expected: one row 'b' +select coalesce(c0, last_value(c0) over (order by c1)) from (select c0, c1 from t1) dt +where c1 = 0 or c0 in (select c0 from t2); + +drop table t1, t2; + --echo # --echo # End of 10.11 tests --echo #