mirror of
https://github.com/MariaDB/server.git
synced 2025-08-09 22:24:09 +03:00
Analysis: -- InnoDB has n (>0) redo-log files. -- In the first page of redo-log there is 2 checkpoint records on fixed location (checkpoint is not encrypted) -- On every checkpoint record there is up to 5 crypt_keys containing the keys used for encryption/decryption -- On crash recovery we read all checkpoints on every file -- Recovery starts by reading from the latest checkpoint forward -- Problem is that latest checkpoint might not always contain the key we need to decrypt all the redo-log blocks (see MDEV-9422 for one example) -- Furthermore, there is no way to identify is the log block corrupted or encrypted For example checkpoint can contain following keys : write chk: 4 [ chk key ]: [ 5 1 ] [ 4 1 ] [ 3 1 ] [ 2 1 ] [ 1 1 ] so over time we could have a checkpoint write chk: 13 [ chk key ]: [ 14 1 ] [ 13 1 ] [ 12 1 ] [ 11 1 ] [ 10 1 ] killall -9 mysqld causes crash recovery and on crash recovery we read as many checkpoints as there is log files, e.g. read [ chk key ]: [ 13 1 ] [ 12 1 ] [ 11 1 ] [ 10 1 ] [ 9 1 ] read [ chk key ]: [ 14 1 ] [ 13 1 ] [ 12 1 ] [ 11 1 ] [ 10 1 ] [ 9 1 ] This is problematic, as we could still scan log blocks e.g. from checkpoint 4 and we do not know anymore the correct key. CRYPT INFO: for checkpoint 14 search 4 CRYPT INFO: for checkpoint 13 search 4 CRYPT INFO: for checkpoint 12 search 4 CRYPT INFO: for checkpoint 11 search 4 CRYPT INFO: for checkpoint 10 search 4 CRYPT INFO: for checkpoint 9 search 4 (NOTE: NOT FOUND) For every checkpoint, code generated a new encrypted key based on key from encryption plugin and random numbers. Only random numbers are stored on checkpoint. Fix: Generate only one key for every log file. If checkpoint contains only one key, use that key to encrypt/decrypt all log blocks. If checkpoint contains more than one key (this is case for databases created using MariaDB server version 10.1.0 - 10.1.12 if log encryption was used). If looked checkpoint_no is found from keys on checkpoint we use that key to decrypt the log block. For encryption we use always the first key. If the looked checkpoint_no is not found from keys on checkpoint we use the first key. Modified code also so that if log is not encrypted, we do not generate any empty keys. If we have a log block and no keys is found from checkpoint we assume that log block is unencrypted. Log corruption or missing keys is found by comparing log block checksums. If we have a keys but current log block checksum is correct we again assume log block to be unencrypted. This is because current implementation stores checksum only before encryption and new checksum after encryption but before disk write is not stored anywhere.
115 lines
3.7 KiB
Plaintext
115 lines
3.7 KiB
Plaintext
-- source include/have_innodb.inc
|
|
-- source include/not_embedded.inc
|
|
-- source filekeys_plugin.inc
|
|
|
|
#
|
|
# MDEV-9011: Redo log encryption does not work
|
|
#
|
|
|
|
--disable_query_log
|
|
let $innodb_file_format_orig = `SELECT @@innodb_file_format`;
|
|
let $innodb_file_per_table_orig = `SELECT @@innodb_file_per_table`;
|
|
--enable_query_log
|
|
|
|
--disable_query_log
|
|
let $innodb_file_format_orig = `SELECT @@innodb_file_format`;
|
|
let $innodb_file_per_table_orig = `SELECT @@innodb_file_per_table`;
|
|
--enable_query_log
|
|
|
|
SET GLOBAL innodb_file_format = `Barracuda`;
|
|
SET GLOBAL innodb_file_per_table = ON;
|
|
|
|
create table t1(c1 bigint not null, b char(200), c varchar(200)) engine=innodb encrypted=yes encryption_key_id=1;
|
|
show warnings;
|
|
|
|
delimiter //;
|
|
create procedure innodb_insert_proc (repeat_count int)
|
|
begin
|
|
declare current_num int;
|
|
set current_num = 0;
|
|
while current_num < repeat_count do
|
|
insert into t1 values(current_num, substring(MD5(RAND()), -64), REPEAT('privatejanprivate',10));
|
|
set current_num = current_num + 1;
|
|
end while;
|
|
end//
|
|
delimiter ;//
|
|
commit;
|
|
|
|
set autocommit=0;
|
|
call innodb_insert_proc(2000);
|
|
commit;
|
|
set autocommit=1;
|
|
|
|
update t1 set c1 = c1 +1;
|
|
select count(*) from t1;
|
|
|
|
-- source include/restart_mysqld.inc
|
|
|
|
--let $MYSQLD_DATADIR=`select @@datadir`
|
|
--let ib1_IBD = $MYSQLD_DATADIR/ibdata1
|
|
--let t1_IBD = $MYSQLD_DATADIR/test/t1.ibd
|
|
--let log0 = $MYSQLD_DATADIR/ib_logfile0
|
|
--let log1 = $MYSQLD_DATADIR/ib_logfile1
|
|
--let SEARCH_RANGE = 10000000
|
|
--let SEARCH_PATTERN=privatejanprivate
|
|
|
|
--echo # ibdata1 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$ib1_IBD
|
|
-- source include/search_pattern_in_file.inc
|
|
--echo # t1 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$t1_IBD
|
|
-- source include/search_pattern_in_file.inc
|
|
--echo # log0 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$log0
|
|
-- source include/search_pattern_in_file.inc
|
|
--echo # log1 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$log1
|
|
-- source include/search_pattern_in_file.inc
|
|
|
|
--echo # Restart mysqld --innodb_encrypt_log=0
|
|
-- let $restart_parameters=--innodb_encrypt_log=0
|
|
-- source include/restart_mysqld.inc
|
|
|
|
insert into t1 values(5000, substring(MD5(RAND()), -64), REPEAT('publicmessage',10));
|
|
insert into t1 values(5001, substring(MD5(RAND()), -64), REPEAT('publicmessage',10));
|
|
insert into t1 values(5002, substring(MD5(RAND()), -64), REPEAT('publicmessage',10));
|
|
insert into t1 values(5003, substring(MD5(RAND()), -64), REPEAT('publicmessage',10));
|
|
insert into t1 values(5004, substring(MD5(RAND()), -64), REPEAT('publicmessage',10));
|
|
|
|
--let SEARCH_PATTERN=privatejanprivate
|
|
--echo # ibdata1 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$ib1_IBD
|
|
-- source include/search_pattern_in_file.inc
|
|
--echo # t1 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$t1_IBD
|
|
-- source include/search_pattern_in_file.inc
|
|
--echo # log0 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$log0
|
|
-- source include/search_pattern_in_file.inc
|
|
--echo # log1 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$log1
|
|
-- source include/search_pattern_in_file.inc
|
|
|
|
--let SEARCH_PATTERN=publicmessage
|
|
--echo # ibdata1 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$ib1_IBD
|
|
-- source include/search_pattern_in_file.inc
|
|
--echo # t1 yes on expecting NOT FOUND
|
|
-- let SEARCH_FILE=$t1_IBD
|
|
-- source include/search_pattern_in_file.inc
|
|
--echo # log0 no on expecting FOUND/NOTFOUND depending where insert goes
|
|
-- let SEARCH_FILE=$log0
|
|
-- source include/search_pattern_in_file.inc
|
|
--echo # log1 no on expecting FOUND/NOTFOUND depending where insert goes
|
|
-- let SEARCH_FILE=$log1
|
|
-- source include/search_pattern_in_file.inc
|
|
|
|
drop procedure innodb_insert_proc;
|
|
drop table t1;
|
|
|
|
# reset system
|
|
--disable_query_log
|
|
EVAL SET GLOBAL innodb_file_per_table = $innodb_file_per_table_orig;
|
|
EVAL SET GLOBAL innodb_file_format = $innodb_file_format_orig;
|
|
--enable_query_log
|