From 26823fe344e7f36f6aa4bb93e790fb52e3da87dc Mon Sep 17 00:00:00 2001 From: Aleksey Midenkov Date: Wed, 16 Sep 2026 12:40:35 +0300 Subject: [PATCH 1/4] MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed. slave_close_thread_tables() unconditionally called trans_commit_stmt()/trans_rollback_stmt(), which assert !thd->in_sub_stmt. A BINLOG statement with malformed base64 payload executed from an AFTER INSERT trigger hits this: on decode failure, mysql_client_binlog_statement() sets thd->is_error() and calls slave_close_thread_tables() from within the trigger's sub-statement. The fix refuses BINLOG in stored routines, triggers, events and anonymous blocks with ER_SP_BADSTATEMENT at parse time, like LOCK, HANDLER etc. Dynamic SQL (EXECUTE IMMEDIATE, EXECUTE) run from a routine bypasses the parser check, so mysql_client_binlog_statement() checks spcont as well: in a stored procedure in_sub_stmt is 0, but the decoded events assert !spcont in reset_for_next_command(). 1. BINLOG statement executed from a trigger, SF or SP is disabled by the patch: Row events (Rows_log_event) open their target table via a one-shot check that only fires at the top of a fresh statement. Inside a trigger, the table is never opened this way. It may be fixed by reusing query_tables, but: - find_locked_table() matches only by table name -- it could return the TABLE instance the enclosing statement is actively writing through, not an idle one. Reusing it would require pre-saving its state: record[0]/bitmaps/handler/etc. (and would bypass ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG) - set_stmt_row_injection()/set_time() calls would mutate thd->lex, which at that point is main_lex -- shared with the enclosing statement, not something safe to touch. Statement events (Query_log_event) run the embedded query via mysql_parse(): it bundles lex_start(), reset_for_next_command() and parse_sql() as one unit meant for a genuinely new top-level statement, not a one-off nested parse. Calling parse_sql() directly instead avoids that, but then we own everything mysql_parse() was doing for us: a private LEX and a private Query_arena (or allocations land on main_lex/whatever arena is currently active, shared with the enclosing statement), plus calling mysql_execute_command() ourselves afterwards. In any case, DML for query_tables cannot be done due to ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG reasons explained above. 2. PS for BINLOG statement still works and needs a leak fix for statement events: A BINLOG statement decoding to a Query_log_event, executed via PREPARE/EXECUTE, leaked its nested query's allocations onto the PS's own persistent arena: thd->stmt_arena pointed at it while mysql_parse() ran the decoded query. Second EXECUTE asserted on ROOT_FLAG_READ_ONLY, since PROTECT_STATEMENT_MEMROOT marks that arena read-only after a successful execution. The fix redirects thd->stmt_arena to thd itself for the duration of the event application, so stmt_arena->is_conventional() reads true and activate_stmt_arena_if_needed() (called e.g. from save_leaf_tables()) never redirects allocations to the PS's arena in the first place. Harmless for what it's protecting: leaf_tables_exec is normally cached on the persistent arena so a repeatedly-executed statement's SELECT_LEX doesn't rebuild it every time, but our SELECT_LEX is torn down and reparsed fresh (due to mysql_parse() semantics) on every EXECUTE, so there's nothing to cache here regardless of which arena is used. --- .../suite/binlog/r/binlog_base64_flag.result | 71 ++++++++++++++ .../suite/binlog/t/binlog_base64_flag.test | 98 +++++++++++++++++++ sql/sql_binlog.cc | 14 +++ sql/sql_yacc.yy | 4 + 4 files changed, 187 insertions(+) diff --git a/mysql-test/suite/binlog/r/binlog_base64_flag.result b/mysql-test/suite/binlog/r/binlog_base64_flag.result index ef92e63eb93..ac08a3fb94d 100644 --- a/mysql-test/suite/binlog/r/binlog_base64_flag.result +++ b/mysql-test/suite/binlog/r/binlog_base64_flag.result @@ -133,3 +133,74 @@ BINLOG '-2079193929'; ERROR HY000: Decoding of base64 string failed BINLOG 'xç↓%~∙D╒ƒ╡'; ERROR HY000: Decoding of base64 string failed +# +# MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed. +# +create table t0 (a int); +create table t1 (a int); +## Case 1: Trigger (disabled) +create trigger trig_t0_insert after insert on t0 for each row binlog 'enabled'; +ERROR 0A000: BINLOG is not allowed in stored procedures +create trigger trig_t0_insert after insert on t0 for each row +binlog @a, @b; +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 2: SP (disabled) +create procedure proc_t1_insert() binlog 'AAAAAAIBAAAAPwAAAAAAAAAAAAEAAAAAAAAABAAAAAB0ZXN0AGluc2VydCBpbnRvIHQxIHZhbHVlcyAoOTkp'; +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 3: SF (disabled) +create function func_t1_insert() returns int +begin +binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='; +return 0; +end| +ERROR 0A000: BINLOG is not allowed in stored procedures +create function func_t1_insert() returns int +begin +binlog @a, @b; +return 0; +end| +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 4: Event (disabled) +create event ev_t1_insert on schedule every 1 day do +binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='; +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 5: Anonymous block (disabled) +begin not atomic +binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='; +end| +ERROR 0A000: BINLOG is not allowed in stored procedures +## Case 6: Dynamic SQL in SP (disabled) +create procedure proc_t1_insert() +execute immediate "binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='"; +call proc_t1_insert(); +ERROR 0A000: BINLOG is not allowed in stored procedures +drop procedure proc_t1_insert; +prepare stmt from "binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='"; +create procedure proc_t1_insert() execute stmt; +call proc_t1_insert(); +ERROR 0A000: BINLOG is not allowed in stored procedures +drop procedure proc_t1_insert; +deallocate prepare stmt; +## Case 7: Row event in PS (works) +binlog 'ODdYRw8BAAAAZgAAAGoAAAABAAQANS4xLjIzLXJjLWRlYnVnLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4N1hHEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC'; +prepare stmt from "binlog 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA=='"; +execute stmt; +execute stmt; +deallocate prepare stmt; +select * from t1; +a +3 +3 +## Case 8: Statement event in PS (works) +prepare stmt from "binlog 'AAAAAAIBAAAAPwAAAAAAAAAAAAEAAAAAAAAABAAAAAB0ZXN0AGluc2VydCBpbnRvIHQxIHZhbHVlcyAoOTkp'"; +execute stmt; +execute stmt; +deallocate prepare stmt; +select * from t1; +a +3 +3 +99 +99 +drop table t0, t1; +# End of 10.11 tests diff --git a/mysql-test/suite/binlog/t/binlog_base64_flag.test b/mysql-test/suite/binlog/t/binlog_base64_flag.test index c2e3d583822..c1c51cf5fe7 100644 --- a/mysql-test/suite/binlog/t/binlog_base64_flag.test +++ b/mysql-test/suite/binlog/t/binlog_base64_flag.test @@ -188,3 +188,101 @@ BINLOG '123'; BINLOG '-2079193929'; --error ER_BASE64_DECODE_ERROR BINLOG 'xç↓%~∙D╒ƒ╡'; + +--echo # +--echo # MDEV-39074 trans_rollback_stmt(THD *): Assertion `! thd->in_sub_stmt' failed. +--echo # + +create table t0 (a int); +create table t1 (a int); + +# Format_description_log_event, same as in the FD test above +--let $FORMAT_DESCR_LOG_EVENT= 'ODdYRw8BAAAAZgAAAGoAAAABAAQANS4xLjIzLXJjLWRlYnVnLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4N1hHEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC' + +# table_map+write_rows_log_event corresponding to: insert into t1 values (3) +--let $ROW_INSERT_T1= 'TFtYRxMBAAAAKQAAAH8BAAAAABAAAAAAAAAABHRlc3QAAnQxAAEDAAE=TFtYRxcBAAAAIgAAAKEBAAAQABAAAAAAAAEAAf/+AwAAAA==' + +# Query_log_event (no checksum, matches the checksum-less FD event above) +# corresponding to: insert into t1 values (99) +--let $STMT_INSERT_T1= 'AAAAAAIBAAAAPwAAAAAAAAAAAAEAAAAAAAAABAAAAAB0ZXN0AGluc2VydCBpbnRvIHQxIHZhbHVlcyAoOTkp' + +--echo ## Case 1: Trigger (disabled) + +--error ER_SP_BADSTATEMENT +create trigger trig_t0_insert after insert on t0 for each row binlog 'enabled'; +--error ER_SP_BADSTATEMENT +create trigger trig_t0_insert after insert on t0 for each row +binlog @a, @b; + +--echo ## Case 2: SP (disabled) + +--error ER_SP_BADSTATEMENT +eval create procedure proc_t1_insert() binlog $STMT_INSERT_T1; + +--echo ## Case 3: SF (disabled) + +delimiter |; +--error ER_SP_BADSTATEMENT +eval create function func_t1_insert() returns int +begin + binlog $ROW_INSERT_T1; + return 0; +end| +--error ER_SP_BADSTATEMENT +create function func_t1_insert() returns int +begin + binlog @a, @b; + return 0; +end| +delimiter ;| + +--echo ## Case 4: Event (disabled) + +--error ER_SP_BADSTATEMENT +eval create event ev_t1_insert on schedule every 1 day do +binlog $ROW_INSERT_T1; + +--echo ## Case 5: Anonymous block (disabled) + +delimiter |; +--error ER_SP_BADSTATEMENT +eval begin not atomic + binlog $ROW_INSERT_T1; +end| +delimiter ;| + +--echo ## Case 6: Dynamic SQL in SP (disabled) + +eval create procedure proc_t1_insert() +execute immediate "binlog $ROW_INSERT_T1"; +--error ER_SP_BADSTATEMENT +call proc_t1_insert(); +drop procedure proc_t1_insert; + +eval prepare stmt from "binlog $ROW_INSERT_T1"; +create procedure proc_t1_insert() execute stmt; +--error ER_SP_BADSTATEMENT +call proc_t1_insert(); +drop procedure proc_t1_insert; +deallocate prepare stmt; + +--echo ## Case 7: Row event in PS (works) + +eval binlog $FORMAT_DESCR_LOG_EVENT; +eval prepare stmt from "binlog $ROW_INSERT_T1"; +execute stmt; +execute stmt; +deallocate prepare stmt; +select * from t1; + +--echo ## Case 8: Statement event in PS (works) + +eval prepare stmt from "binlog $STMT_INSERT_T1"; +execute stmt; +execute stmt; +deallocate prepare stmt; +select * from t1; + +drop table t0, t1; + +--echo # End of 10.11 tests diff --git a/sql/sql_binlog.cc b/sql/sql_binlog.cc index 118b361eb79..85e5f006c29 100644 --- a/sql/sql_binlog.cc +++ b/sql/sql_binlog.cc @@ -244,6 +244,16 @@ void mysql_client_binlog_statement(THD* thd) thd->lex->comment.length : 2048), thd->lex->comment.str)); + /* + The parser refuses BINLOG in a routine body; this catches dynamic SQL + (EXECUTE IMMEDIATE, EXECUTE) run from a routine. + */ + if (thd->spcont) + { + my_error(ER_SP_BADSTATEMENT, MYF(0), "BINLOG"); + DBUG_VOID_RETURN; + } + if (check_global_access(thd, PRIV_STMT_BINLOG)) DBUG_VOID_RETURN; @@ -421,8 +431,12 @@ void mysql_client_binlog_statement(THD* thd) applied in PS mode. So, reset the current lex temporary to point to thd->main_lex before running ev->apply_event() and restore its original value on return. + + Likewise, point thd->stmt_arena to thd: otherwise a Query event's + statement leaks its allocations onto the PS's persistent arena. */ LEX *backup_lex; + SCOPE_VALUE(thd->stmt_arena, static_cast(thd)); thd->backup_and_reset_current_lex(&backup_lex); err= save_restore_context_apply_event(ev, rgi); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 3e96888ea68..c6631cae5d5 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -8231,6 +8231,8 @@ table_index_name: binlog_base64_event: BINLOG_SYM TEXT_STRING_sys { + if (Lex->sphead) + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "BINLOG")); Lex->sql_command = SQLCOM_BINLOG_BASE64_EVENT; Lex->comment= $2; Lex->ident.str= NULL; @@ -8239,6 +8241,8 @@ binlog_base64_event: | BINLOG_SYM '@' ident_or_text ',' '@' ident_or_text { + if (Lex->sphead) + my_yyabort_error((ER_SP_BADSTATEMENT, MYF(0), "BINLOG")); Lex->sql_command = SQLCOM_BINLOG_BASE64_EVENT; Lex->comment= $3; Lex->ident= $6; From 2f3e758663ead4873a666781b436440a603d13f5 Mon Sep 17 00:00:00 2001 From: Elena Stepanova Date: Thu, 15 Oct 2026 22:54:03 +0300 Subject: [PATCH 2/4] MDEV-41085 Enable MTR tests on Windows: rpl suite rpl.rpl_change_master_long_channel_name fails on Windows because it expects a 252-character name (base name, excluding the path) to be valid; on Windows the whole path is limited to 260 characters, so 252-character name already fails --- mysql-test/suite/rpl/t/rpl_change_master_long_channel_name.test | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/suite/rpl/t/rpl_change_master_long_channel_name.test b/mysql-test/suite/rpl/t/rpl_change_master_long_channel_name.test index 1cb94c87f5a..9c3315a7703 100644 --- a/mysql-test/suite/rpl/t/rpl_change_master_long_channel_name.test +++ b/mysql-test/suite/rpl/t/rpl_change_master_long_channel_name.test @@ -10,6 +10,7 @@ # 3. Verify the server is still usable: a normal CHANGE MASTER for a # new, short-named channel succeeds. # +--source include/not_windows.inc --source include/master-slave.inc --connection slave From 91990beecc87072dc76ae519664e68a5547a05be Mon Sep 17 00:00:00 2001 From: Elena Stepanova Date: Thu, 15 Oct 2026 22:57:28 +0300 Subject: [PATCH 3/4] MDEV-41085 Enable MTR tests on Windows: rpl suite rpl.rpl_read_old_relay_log_info fails because the test overrrides relay-log.info with the Linux-style ./slave-relay-bin.000001, while Windows uses the format with backslashes, .\slave-relay-bin.000001 --- .../rpl/t/rpl_read_old_relay_log_info.test | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/mysql-test/suite/rpl/t/rpl_read_old_relay_log_info.test b/mysql-test/suite/rpl/t/rpl_read_old_relay_log_info.test index db910d80c41..b8f103bd171 100644 --- a/mysql-test/suite/rpl/t/rpl_read_old_relay_log_info.test +++ b/mysql-test/suite/rpl/t/rpl_read_old_relay_log_info.test @@ -52,13 +52,22 @@ if (`SELECT "$master_file" != "" OR --source include/stop_slave.inc # Create a relay-log.info file with a position value > 2^31 ---let $MYSQLD_DATADIR= `select @@datadir` ---remove_file $MYSQLD_DATADIR/relay-log.info ---write_file $MYSQLD_DATADIR/relay-log.info -./slave-relay-bin.000001 -2147483648 +--let MYSQLD_DATADIR= `select @@datadir` +perl; +my $file = "$ENV{MYSQLD_DATADIR}/relay-log.info"; +my $newpos = '2147483648'; -0 +open(my $in, "<", $file) or die "Cannot open $file: $!"; +binmode($in); # To preserve line endings +my @lines = <$in>; +close($in); + +$lines[1] = "$newpos\n"; + +open(my $out, ">", $file) or die "Cannot write to $file: $!"; +binmode($out); # To preserve line endings +print $out @lines; +close($out) or die "Error closing $file: $!"; EOF --let $rpl_server_number= 2 From 5a37a47623d32c47cf379ca1f571adf9fbfc3fa0 Mon Sep 17 00:00:00 2001 From: Elena Stepanova Date: Thu, 15 Oct 2026 22:59:36 +0300 Subject: [PATCH 4/4] MDEV-41085 Enable MTR tests on Windows: rpl suite rpl.rpl_row_mysqlbinlog fails because apparently the client command line on Windows cannot interpret the literal cp932 string which is passed to it. Since the point of the test is not how the original INSERT happens, but how it further works while replaying the binlog, it should be acceptable to insert a hex-encoded value --- mysql-test/suite/rpl/t/rpl_row_mysqlbinlog.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-test/suite/rpl/t/rpl_row_mysqlbinlog.test b/mysql-test/suite/rpl/t/rpl_row_mysqlbinlog.test index 9e10aaa6216..dde21d85975 100644 --- a/mysql-test/suite/rpl/t/rpl_row_mysqlbinlog.test +++ b/mysql-test/suite/rpl/t/rpl_row_mysqlbinlog.test @@ -238,7 +238,7 @@ connection master; create table t4 (f text character set utf8); create table t5 (f text character set cp932); --exec $MYSQL --default-character-set=utf8 test -e "insert into t4 values(_utf8'ソ')" ---exec $MYSQL --default-character-set=cp932 test -e "insert into t5 values(_cp932'ƒ\');" +--exec $MYSQL --default-character-set=cp932 test -e "insert into t5 values(UNHEX('835C'))" flush logs; rename table t4 to t04, t5 to t05; --exec $MYSQL_BINLOG $MYSQLD_DATADIR/master-bin.000001 | $MYSQL --default-character-set=utf8