Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions mysql-test/suite/maria/maria-recovery-corrupt-del-prefix.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
call mtr.add_suppression("File '.*aria_log.000.*' not found");
drop database if exists mysqltest;
create database mysqltest;
connect admin, localhost, root,,mysqltest,,;
connection default;
use mysqltest;
connection default;
connection admin;
* shut down mysqld, removed logs, restarted it
connection default;
create table t1 (a int, b varchar(200), key(b))
transactional=1 row_format=page engine=aria;
insert into t1 (a,b)
select seq, concat('k',lpad(seq*7919%100003,6,'0'),repeat('q',150))
from seq_1_to_2000;
set session debug_dbug="+d,corrupt_del_prefix";
delete from t1 where a % 4 <> 0;
select count(*) from t1;
count(*)
500
set session debug_dbug="+d,maria_flush_whole_log,maria_crash";
set global aria_checkpoint_interval=1;
ERROR HY000: Lost connection to server during query
# Recovery must refuse the record, not apply it and crash on the bmove.
FOUND 1 /Aria engine: Redo phase failed/ in mdev40494.err
NOT FOUND /AddressSanitizer|got signal 11/ in mdev40494.err
drop database mysqltest;
85 changes: 85 additions & 0 deletions mysql-test/suite/maria/maria-recovery-corrupt-del-prefix.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# MDEV-40494: _ma_apply_redo_index() subtracted KEY_OP_DEL_PREFIX's logged
# length from page_length guarded only by a DBUG_ASSERT, which release builds
# compile out. A length larger than the used page underflows the bmove size to
# about 4G and wraps page_length.

--source include/not_embedded.inc
--source include/have_debug.inc
--source include/have_maria.inc
--source include/have_sequence.inc
# the server is killed on purpose below
--source include/not_valgrind.inc

call mtr.add_suppression("File '.*aria_log.000.*' not found");

let $MYSQLD_DATADIR= `select @@datadir`;
let $MARIA_LOG=.;

--disable_warnings
drop database if exists mysqltest;
--enable_warnings
create database mysqltest;

# the log is shared with earlier tests, and recovery below replays all of it
connect (admin, localhost, root,,mysqltest,,);
--enable_reconnect
connection default;
use mysqltest;
--enable_reconnect
--source include/maria_empty_logs.inc

# Long keys so the index splits, then a wide delete so pages underflow and
# merge. Only that path logs KEY_OP_DEL_PREFIX; plain inserts never do.
create table t1 (a int, b varchar(200), key(b))
transactional=1 row_format=page engine=aria;
insert into t1 (a,b)
select seq, concat('k',lpad(seq*7919%100003,6,'0'),repeat('q',150))
from seq_1_to_2000;

# Forge the logged length of every KEY_OP_DEL_PREFIX below. org_size is the
# whole used page, which is keypage_header bytes more than the branch allows.
set session debug_dbug="+d,corrupt_del_prefix";
delete from t1 where a % 4 <> 0;
select count(*) from t1;

# Kill without flushing pages, so recovery has to replay the forged records
--write_line wait $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
set session debug_dbug="+d,maria_flush_whole_log,maria_crash";
--error 2013
set global aria_checkpoint_interval=1;

--echo # Recovery must refuse the record, not apply it and crash on the bmove.
# Failed recovery aborts the server, so --bootstrap drives it and exits by
# itself. A failed redo phase leaves pages pinned, so a debug build then trips
# the pagecache cleanup assert on the way out; the log below is the assertion,
# not the exit code.
--write_file $MYSQLTEST_VARDIR/tmp/mdev40494_boot.sql
select 1;
EOF

--error 1,134
--exec $MYSQLD_CMD --bootstrap --log-error=$MYSQLTEST_VARDIR/tmp/mdev40494.err < $MYSQLTEST_VARDIR/tmp/mdev40494_boot.sql > $MYSQLTEST_VARDIR/tmp/mdev40494.out 2>&1

--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mdev40494.err
--let SEARCH_PATTERN= Aria engine: Redo phase failed
--source include/search_pattern_in_file.inc

# Unfixed, the bmove runs with a ~4G size and the process dies on it: a
# sanitizer report where there is one, SIGSEGV where there is not. The refusal
# above ends in SIGABRT instead, so this stays NOT FOUND on both.
--let SEARCH_PATTERN= AddressSanitizer|got signal 11
--source include/search_pattern_in_file.inc

# every prefix delete in this log is forged, it can never be replayed
--error 0,1
remove_files_wildcard $MYSQLD_DATADIR aria_log.0*;

--write_line restart $MYSQLTEST_VARDIR/tmp/mysqld.1.expect
--enable_reconnect
--source include/wait_until_connected_again.inc

drop database mysqltest;

remove_file $MYSQLTEST_VARDIR/tmp/mdev40494.err;
remove_file $MYSQLTEST_VARDIR/tmp/mdev40494.out;
remove_file $MYSQLTEST_VARDIR/tmp/mdev40494_boot.sql;
11 changes: 9 additions & 2 deletions storage/maria/ma_key_recover.c
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,9 @@ my_bool _ma_log_prefix(MARIA_PAGE *ma_page, uint changed_length,
{
/* Delete prefix */
log_pos[0]= KEY_OP_DEL_PREFIX;
int2store(log_pos+1, -move_length);
int2store(log_pos+1,
DBUG_IF("corrupt_del_prefix") ? ma_page->org_size
: (uint) -move_length);
log_pos+= 3;
if (changed_length)
{
Expand Down Expand Up @@ -1046,7 +1048,12 @@ uint _ma_apply_redo_index(MARIA_HA *info,
uint length= uint2korr(header);
header+= 2;
DBUG_PRINT("redo", ("key_op_del_prefix: %u", length));
DBUG_ASSERT(length <= page_length - keypage_header);
if (unlikely(keypage_header + length > page_length))
{
DBUG_ASSERT(!maria_assert_if_crashed_table);
result= mark_crashed= 1;
goto err;
}

bmove(buff + keypage_header, buff + keypage_header +
length, page_length - keypage_header - length);
Expand Down