1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-31567 SIGSEGV in reinit_stmt_before_use | Prepared_statement::execute

If the first execution of a prepared statement was killed (for
example by max_statement_time) after the top-level JOIN had done the
once-per-statement semi-join rewrite of the nested subqueries, but
before the nested selects saved their rewritten WHERE into prep_where,
the statement was left half-transformed. A later execution then used
a freed Item from prep_where or an unfixed Item_field and crashed.

Set LEX::needs_reprepare when JOIN::optimize() fails during the
select's first optimization, so the statement is re-prepared before
it runs again.

Also make the handling of needs_reprepare safe when the re-prepare
fails: keep the flag so it is tried again, do not skip a pending
long data error, and invalidate the SP instruction so that a failed
re-parse is retried.
This commit is contained in:
Khaled Riyad
2026-10-05 18:38:18 +03:00
parent 0ff45029fd
commit e2f9b2ac30
6 changed files with 229 additions and 6 deletions

View File

@@ -265,3 +265,102 @@ ERROR 70100: Query execution was interrupted
DROP TABLE t1;
disconnect con1;
SET debug_sync='RESET';
#
# MDEV-31567 SIGSEGV in reinit_stmt_before_use | Prepared_statement::execute
#
# The first EXECUTE is killed after the one-time semi-join rewrite, but
# before the nested selects save their WHERE. The statement must be
# re-prepared, not run half-rewritten. The bug was reported with
# max_statement_time, KILL QUERY at a sync point makes it deterministic.
CREATE TABLE t (a INT);
connect con1,localhost,root,,;
PREPARE s FROM <62 nested IN subqueries>
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
EXECUTE s;
connection default;
SET debug_sync='now WAIT_FOR parked';
KILL QUERY id;
SET debug_sync='now SIGNAL go';
connection con1;
ERROR 70100: Query execution was interrupted
SET debug_sync='RESET';
EXECUTE s;
a
EXECUTE s;
a
DEALLOCATE PREPARE s;
# The same with rows, the results must be correct
INSERT INTO t VALUES (1),(2),(3);
PREPARE s FROM <62 nested IN subqueries>
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
EXECUTE s;
connection default;
SET debug_sync='now WAIT_FOR parked';
KILL QUERY id;
SET debug_sync='now SIGNAL go';
connection con1;
ERROR 70100: Query execution was interrupted
SET debug_sync='RESET';
EXECUTE s;
a
1
EXECUTE s;
a
1
DEALLOCATE PREPARE s;
# The same in a stored procedure
CREATE PROCEDURE p() <62 nested IN subqueries>
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
CALL p();
connection default;
SET debug_sync='now WAIT_FOR parked';
KILL QUERY id;
SET debug_sync='now SIGNAL go';
connection con1;
ERROR 70100: Query execution was interrupted
SET debug_sync='RESET';
CALL p();
a
1
CALL p();
a
1
DROP PROCEDURE p;
connection default;
disconnect con1;
# If the re-prepare fails, it must be tried again on the next EXECUTE
CREATE USER u@localhost;
GRANT SUPER ON *.* TO u@localhost;
GRANT SELECT ON test.t TO u@localhost;
connect con2,localhost,u,,test;
PREPARE s FROM <62 nested IN subqueries>
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
EXECUTE s;
connection default;
SET debug_sync='now WAIT_FOR parked';
KILL QUERY id;
SET debug_sync='now SIGNAL go';
REVOKE SELECT ON test.t FROM u@localhost;
connection con2;
ERROR 70100: Query execution was interrupted
SET debug_sync='RESET';
EXECUTE s;
ERROR 42000: SELECT command denied to user 'u'@'localhost' for table `test`.`t`
SHOW STATUS LIKE 'Com_stmt_reprepare';
Variable_name Value
Com_stmt_reprepare 1
connection default;
GRANT SELECT ON test.t TO u@localhost;
connection con2;
EXECUTE s;
a
1
SHOW STATUS LIKE 'Com_stmt_reprepare';
Variable_name Value
Com_stmt_reprepare 2
connection default;
disconnect con2;
DROP USER u@localhost;
DROP TABLE t;
SET debug_sync='RESET';
# End of 11.4 tests

View File

@@ -361,3 +361,120 @@ reap;
DROP TABLE t1;
disconnect con1;
SET debug_sync='RESET';
--echo #
--echo # MDEV-31567 SIGSEGV in reinit_stmt_before_use | Prepared_statement::execute
--echo #
--echo # The first EXECUTE is killed after the one-time semi-join rewrite, but
--echo # before the nested selects save their WHERE. The statement must be
--echo # re-prepared, not run half-rewritten. The bug was reported with
--echo # max_statement_time, KILL QUERY at a sync point makes it deterministic.
CREATE TABLE t (a INT);
let $q= 1;
let $d= 62;
while ($d)
{
let $q= SELECT a FROM t WHERE a IN ($q);
dec $d;
}
connect (con1,localhost,root,,);
let $id= `SELECT CONNECTION_ID()`;
--echo PREPARE s FROM <62 nested IN subqueries>
--disable_query_log
eval PREPARE s FROM '$q';
--enable_query_log
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
send EXECUTE s;
connection default;
SET debug_sync='now WAIT_FOR parked';
replace_result $id id;
eval KILL QUERY $id;
SET debug_sync='now SIGNAL go';
connection con1;
--error ER_QUERY_INTERRUPTED
reap;
SET debug_sync='RESET';
EXECUTE s;
EXECUTE s;
DEALLOCATE PREPARE s;
--echo # The same with rows, the results must be correct
INSERT INTO t VALUES (1),(2),(3);
--echo PREPARE s FROM <62 nested IN subqueries>
--disable_query_log
eval PREPARE s FROM '$q';
--enable_query_log
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
send EXECUTE s;
connection default;
SET debug_sync='now WAIT_FOR parked';
replace_result $id id;
eval KILL QUERY $id;
SET debug_sync='now SIGNAL go';
connection con1;
--error ER_QUERY_INTERRUPTED
reap;
SET debug_sync='RESET';
EXECUTE s;
EXECUTE s;
DEALLOCATE PREPARE s;
--echo # The same in a stored procedure
--echo CREATE PROCEDURE p() <62 nested IN subqueries>
--disable_query_log
eval CREATE PROCEDURE p() $q;
--enable_query_log
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
send CALL p();
connection default;
SET debug_sync='now WAIT_FOR parked';
replace_result $id id;
eval KILL QUERY $id;
SET debug_sync='now SIGNAL go';
connection con1;
--error ER_QUERY_INTERRUPTED
reap;
SET debug_sync='RESET';
CALL p();
CALL p();
DROP PROCEDURE p;
connection default;
disconnect con1;
--echo # If the re-prepare fails, it must be tried again on the next EXECUTE
CREATE USER u@localhost;
GRANT SUPER ON *.* TO u@localhost;
GRANT SELECT ON test.t TO u@localhost;
connect (con2,localhost,u,,test);
let $id= `SELECT CONNECTION_ID()`;
--echo PREPARE s FROM <62 nested IN subqueries>
--disable_query_log
eval PREPARE s FROM '$q';
--enable_query_log
SET debug_sync='before_join_optimize SIGNAL parked WAIT_FOR go EXECUTE 1';
send EXECUTE s;
connection default;
SET debug_sync='now WAIT_FOR parked';
replace_result $id id;
eval KILL QUERY $id;
SET debug_sync='now SIGNAL go';
REVOKE SELECT ON test.t FROM u@localhost;
connection con2;
--error ER_QUERY_INTERRUPTED
reap;
SET debug_sync='RESET';
--error ER_TABLEACCESS_DENIED_ERROR
EXECUTE s;
SHOW STATUS LIKE 'Com_stmt_reprepare';
connection default;
GRANT SELECT ON test.t TO u@localhost;
connection con2;
EXECUTE s;
SHOW STATUS LIKE 'Com_stmt_reprepare';
connection default;
disconnect con2;
DROP USER u@localhost;
DROP TABLE t;
SET debug_sync='RESET';
--echo # End of 11.4 tests

View File

@@ -478,7 +478,10 @@ int sp_lex_keeper::validate_lex_and_exec_core(THD *thd, uint *nextp,
while (true)
{
if (instr->is_invalid() || m_lex->needs_reprepare)
if (!instr->is_invalid() && m_lex->needs_reprepare)
instr->invalidate();
if (instr->is_invalid())
{
thd->clear_error();
free_lex(thd);

View File

@@ -3313,9 +3313,9 @@ public:
Explain_query *explain;
/*
If true, query optimizer has encountered an unrecoverable error when doing
If true, query optimizer has encountered an error when doing
once-per-statement optimization and it is not safe to re-execute this
statement.
statement without re-preparing it.
*/
bool needs_reprepare{false};

View File

@@ -4468,13 +4468,12 @@ Prepared_statement::execute_loop(String *expanded_query,
*/
DBUG_ASSERT(thd->free_list == NULL);
if (lex->needs_reprepare)
if (lex->needs_reprepare && state != Query_arena::STMT_ERROR)
{
/*
Something has happened on previous execution that requires us to
re-prepare before we try to execute.
*/
lex->needs_reprepare= false;
goto start_with_reprepare;
}
@@ -4648,7 +4647,6 @@ Prepared_statement::execute_bulk_loop(String *expanded_query,
Something has happened on previous execution that requires us to
re-prepare before we try to execute.
*/
lex->needs_reprepare= false;
error= reprepare();
if (error)
goto err;

View File

@@ -2029,7 +2029,13 @@ int JOIN::optimize()
if (optimization_state != JOIN::NOT_OPTIMIZED)
return FALSE;
optimization_state= JOIN::OPTIMIZATION_IN_PROGRESS;
bool first_optimization= select_lex->first_cond_optimization;
res= optimize_inner();
if (unlikely(res) && first_optimization)
{
/* A failed first optimization may leave the statement half-done */
thd->lex->needs_reprepare= true;
}
}
if (!with_two_phase_optimization)
{