diff --git a/mysql-test/suite/innodb/r/innodb_bug14147491.result b/mysql-test/suite/innodb/r/innodb_bug14147491.result index 1a8249f9e54..ea725dda257 100644 --- a/mysql-test/suite/innodb/r/innodb_bug14147491.result +++ b/mysql-test/suite/innodb/r/innodb_bug14147491.result @@ -12,22 +12,14 @@ CALL mtr.add_suppression("InnoDB: fix the corruption by dumping, dropping, and r CALL mtr.add_suppression("InnoDB: the corrupt table. You can use CHECK"); CALL mtr.add_suppression("InnoDB: TABLE to scan your table for corruption."); CALL mtr.add_suppression("InnoDB: See also .* about forcing recovery."); -flush tables; # Create and populate the table to be corrupted CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY, b TEXT) ENGINE=InnoDB; INSERT INTO t1 (b) VALUES ('corrupt me'); INSERT INTO t1 (b) VALUES ('corrupt me'); -# Write file to make mysql-test-run.pl expect the "crash", but don't -# start it until it's told to -# We give 30 seconds to do a clean shutdown because we do not want -# to redo apply the pages of t1.ibd at the time of recovery. -# We want SQL to initiate the first access to t1.ibd. -# Wait until disconnected. # Backup the t1.ibd before corrupting # Corrupt the table Munged a string. Munged a string. -# Write file to make mysql-test-run.pl start up the server again SET DEBUG_DBUG = '+d,innodb_page_corruption_retries'; # Write file to make mysql-test-run.pl expect the "crash", but don't # start it until it's told to @@ -36,6 +28,5 @@ SET DEBUG_DBUG = '+d,innodb_page_corruption_retries'; SELECT * FROM t1; ERROR HY000: Lost connection to MySQL server during query # Restore the original t1.ibd -# Write file to make mysql-test-run.pl start up the server again # Cleanup DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/innodb_bug14147491.test b/mysql-test/suite/innodb/t/innodb_bug14147491.test index 853c7e63f35..5776b2c2e37 100644 --- a/mysql-test/suite/innodb/t/innodb_bug14147491.test +++ b/mysql-test/suite/innodb/t/innodb_bug14147491.test @@ -33,8 +33,6 @@ CALL mtr.add_suppression("InnoDB: fix the corruption by dumping, dropping, and r CALL mtr.add_suppression("InnoDB: the corrupt table. You can use CHECK"); CALL mtr.add_suppression("InnoDB: TABLE to scan your table for corruption."); CALL mtr.add_suppression("InnoDB: See also .* about forcing recovery."); -flush tables; - --echo # Create and populate the table to be corrupted CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY, b TEXT) ENGINE=InnoDB; @@ -52,16 +50,9 @@ INSERT INTO t1 (b) VALUES ('corrupt me'); let $MYSQLD_DATADIR=`select @@datadir`; let t1_IBD = $MYSQLD_DATADIR/test/t1.ibd; ---echo # Write file to make mysql-test-run.pl expect the "crash", but don't ---echo # start it until it's told to --exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect ---echo # We give 30 seconds to do a clean shutdown because we do not want ---echo # to redo apply the pages of t1.ibd at the time of recovery. ---echo # We want SQL to initiate the first access to t1.ibd. -shutdown_server 30; - ---echo # Wait until disconnected. +--source include/shutdown_mysqld.inc --source include/wait_until_disconnected.inc --echo # Backup the t1.ibd before corrupting @@ -94,10 +85,7 @@ while ($len = sysread IBD_FILE, $chunk, 1024) close IBD_FILE; EOF ---echo # Write file to make mysql-test-run.pl start up the server again ---exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect ---enable_reconnect ---source include/wait_until_connected_again.inc +--source include/start_mysqld.inc SET DEBUG_DBUG = '+d,innodb_page_corruption_retries'; @@ -119,10 +107,7 @@ SLEEP 1; --remove_file $MYSQLD_DATADIR/test/t1.ibd --move_file $MYSQLD_DATADIR/test/t1.ibd.backup $MYSQLD_DATADIR/test/t1.ibd ---echo # Write file to make mysql-test-run.pl start up the server again ---exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.1.expect ---enable_reconnect ---source include/wait_until_connected_again.inc +--source include/start_mysqld.inc # Note SET DEBUG = '-d,innodb_page_corruption_retries' is not required # because the session information is lost after server restart diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 0afbc21af38..9d322d3c833 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -520,8 +520,9 @@ buf_page_is_checksum_valid_crc32( if (!(checksum_field1 == crc32 && checksum_field2 == crc32)) { DBUG_PRINT("buf_checksum", - ("Page checksum crc32 not valid field1 %lu field2 %lu crc32 %lu.", - checksum_field1, checksum_field2, (ulint)crc32)); + ("Page checksum crc32 not valid field1 " ULINTPF + " field2 " ULINTPF " crc32 %u.", + checksum_field1, checksum_field2, crc32)); } @@ -553,7 +554,8 @@ buf_page_is_checksum_valid_innodb( if (checksum_field2 != mach_read_from_4(read_buf + FIL_PAGE_LSN) && checksum_field2 != buf_calc_page_old_checksum(read_buf)) { DBUG_PRINT("buf_checksum", - ("Page checksum innodb not valid field1 %lu field2 %lu crc32 %lu lsn %lu.", + ("Page checksum innodb not valid field1 " ULINTPF + " field2 " ULINTPF "crc32 " ULINTPF " lsn " ULINTPF ".", checksum_field1, checksum_field2, buf_calc_page_old_checksum(read_buf), mach_read_from_4(read_buf + FIL_PAGE_LSN))); @@ -568,7 +570,8 @@ buf_page_is_checksum_valid_innodb( if (checksum_field1 != 0 && checksum_field1 != buf_calc_page_new_checksum(read_buf)) { DBUG_PRINT("buf_checksum", - ("Page checksum innodb not valid field1 %lu field2 %lu crc32 %lu lsn %lu.", + ("Page checksum innodb not valid field1 " ULINTPF + " field2 " ULINTPF "crc32 " ULINTPF " lsn " ULINTPF ".", checksum_field1, checksum_field2, buf_calc_page_new_checksum(read_buf), mach_read_from_4(read_buf + FIL_PAGE_LSN))); @@ -593,7 +596,8 @@ buf_page_is_checksum_valid_none( if (!(checksum_field1 == checksum_field2 && checksum_field1 == BUF_NO_CHECKSUM_MAGIC)) { DBUG_PRINT("buf_checksum", - ("Page checksum none not valid field1 %lu field2 %lu crc32 %lu lsn %lu.", + ("Page checksum none not valid field1 " ULINTPF + " field2 " ULINTPF "crc32 " ULINTPF " lsn " ULINTPF ".", checksum_field1, checksum_field2, BUF_NO_CHECKSUM_MAGIC, mach_read_from_4(read_buf + FIL_PAGE_LSN))); } diff --git a/storage/innobase/fil/fil0crypt.cc b/storage/innobase/fil/fil0crypt.cc index fed1cf94d84..13f2e905bc4 100644 --- a/storage/innobase/fil/fil0crypt.cc +++ b/storage/innobase/fil/fil0crypt.cc @@ -981,8 +981,9 @@ fil_space_verify_crypt_checksum( /* Declare empty pages non-corrupted */ if (checksum == 0 - && *reinterpret_cast(page + FIL_PAGE_LSN) == 0) { - return (true); + && *reinterpret_cast(page + FIL_PAGE_LSN) == 0 + && buf_page_is_zeroes(page, zip_size)) { + return(true); } /* Compressed and encrypted pages do not have checksum. Assume not @@ -1016,16 +1017,31 @@ fil_space_verify_crypt_checksum( bool encrypted = (checksum == cchecksum1 || checksum == cchecksum2 || checksum == BUF_NO_CHECKSUM_MAGIC); - /* Old InnoDB versions did not initialize - FIL_PAGE_FILE_FLUSH_LSN field so there could be garbage - and above checksum check could produce false positive. - Thus we also check does the traditional stored - checksum fields match the calculated one. Both of these - could naturally produce false positive but then - we just decrypt the page and after that corrupted - pages very probable stay corrupted and valid - pages very probable stay valid. + /* MySQL 5.6 and MariaDB 10.0 and 10.1 will write an LSN to the + first page of each system tablespace file at + FIL_PAGE_FILE_FLUSH_LSN offset. On other pages and in other files, + the field might have been uninitialized until MySQL 5.5. In MySQL 5.7 + (and MariaDB Server 10.2.2) WL#7990 stopped writing the field for other + than page 0 of the system tablespace. + + Starting from MariaDB 10.1 the field has been repurposed for + encryption key_version. + + Starting with MySQL 5.7 (and MariaDB Server 10.2), the + field has been repurposed for SPATIAL INDEX pages for + FIL_RTREE_SPLIT_SEQ_NUM. + + Note that FIL_PAGE_FILE_FLUSH_LSN is not included in the InnoDB page + checksum. + + Thus, FIL_PAGE_FILE_FLUSH_LSN could contain any value. While the + field would usually be 0 for pages that are not encrypted, we cannot + assume that a nonzero value means that the page is encrypted. + Therefore we must validate the page both as encrypted and unencrypted + when FIL_PAGE_FILE_FLUSH_LSN does not contain 0. + */ + ulint checksum1 = mach_read_from_4( page + FIL_PAGE_SPACE_OR_CHKSUM); diff --git a/storage/innobase/include/buf0buf.h b/storage/innobase/include/buf0buf.h index 25f6fae76e9..a0109b4aa64 100644 --- a/storage/innobase/include/buf0buf.h +++ b/storage/innobase/include/buf0buf.h @@ -1759,7 +1759,7 @@ struct buf_page_t{ 0 if the block was never accessed in the buffer pool. Protected by block mutex */ - ibool is_corrupt; + # if defined UNIV_DEBUG_FILE_ACCESSES || defined UNIV_DEBUG ibool file_page_was_freed; /*!< this is set to TRUE when diff --git a/storage/xtradb/buf/buf0buf.cc b/storage/xtradb/buf/buf0buf.cc index d0bfa2de344..e1746240ef5 100644 --- a/storage/xtradb/buf/buf0buf.cc +++ b/storage/xtradb/buf/buf0buf.cc @@ -585,8 +585,9 @@ buf_page_is_checksum_valid_crc32( if (!(checksum_field1 == crc32 && checksum_field2 == crc32)) { DBUG_PRINT("buf_checksum", - ("Page checksum crc32 not valid field1 %lu field2 %lu crc32 %lu.", - checksum_field1, checksum_field2, (ulint)crc32)); + ("Page checksum crc32 not valid field1 " ULINTPF + " field2 " ULINTPF " crc32 %u.", + checksum_field1, checksum_field2, crc32)); } return(checksum_field1 == crc32 && checksum_field2 == crc32); @@ -618,7 +619,8 @@ buf_page_is_checksum_valid_innodb( && checksum_field2 != buf_calc_page_old_checksum(read_buf)) { DBUG_PRINT("buf_checksum", - ("Page checksum innodb not valid field1 %lu field2 %lu crc32 %lu lsn %lu.", + ("Page checksum innodb not valid field1 " ULINTPF + " field2 " ULINTPF "crc32 " ULINTPF " lsn " ULINTPF ".", checksum_field1, checksum_field2, buf_calc_page_old_checksum(read_buf), mach_read_from_4(read_buf + FIL_PAGE_LSN))); @@ -634,7 +636,8 @@ buf_page_is_checksum_valid_innodb( && checksum_field1 != buf_calc_page_new_checksum(read_buf)) { DBUG_PRINT("buf_checksum", - ("Page checksum innodb not valid field1 %lu field2 %lu crc32 %lu lsn %lu.", + ("Page checksum innodb not valid field1 " ULINTPF + " field2 " ULINTPF "crc32 " ULINTPF " lsn " ULINTPF ".", checksum_field1, checksum_field2, buf_calc_page_new_checksum(read_buf), mach_read_from_4(read_buf + FIL_PAGE_LSN))); @@ -659,7 +662,8 @@ buf_page_is_checksum_valid_none( if (!(checksum_field1 == checksum_field2 && checksum_field1 == BUF_NO_CHECKSUM_MAGIC)) { DBUG_PRINT("buf_checksum", - ("Page checksum none not valid field1 %lu field2 %lu crc32 %lu lsn %lu.", + ("Page checksum none not valid field1 " ULINTPF + " field2 " ULINTPF "crc32 " ULINTPF " lsn " ULINTPF ".", checksum_field1, checksum_field2, BUF_NO_CHECKSUM_MAGIC, mach_read_from_4(read_buf + FIL_PAGE_LSN))); } diff --git a/storage/xtradb/fil/fil0crypt.cc b/storage/xtradb/fil/fil0crypt.cc index fed1cf94d84..f4a27afb969 100644 --- a/storage/xtradb/fil/fil0crypt.cc +++ b/storage/xtradb/fil/fil0crypt.cc @@ -981,8 +981,9 @@ fil_space_verify_crypt_checksum( /* Declare empty pages non-corrupted */ if (checksum == 0 - && *reinterpret_cast(page + FIL_PAGE_LSN) == 0) { - return (true); + && *reinterpret_cast(page + FIL_PAGE_LSN) == 0 + && buf_page_is_zeroes(page, zip_size)) { + return(true); } /* Compressed and encrypted pages do not have checksum. Assume not @@ -1016,16 +1017,30 @@ fil_space_verify_crypt_checksum( bool encrypted = (checksum == cchecksum1 || checksum == cchecksum2 || checksum == BUF_NO_CHECKSUM_MAGIC); - /* Old InnoDB versions did not initialize - FIL_PAGE_FILE_FLUSH_LSN field so there could be garbage - and above checksum check could produce false positive. - Thus we also check does the traditional stored - checksum fields match the calculated one. Both of these - could naturally produce false positive but then - we just decrypt the page and after that corrupted - pages very probable stay corrupted and valid - pages very probable stay valid. + /* MySQL 5.6 and MariaDB 10.0 and 10.1 will write an LSN to the + first page of each system tablespace file at + FIL_PAGE_FILE_FLUSH_LSN offset. On other pages and in other files, + the field might have been uninitialized until MySQL 5.5. In MySQL 5.7 + (and MariaDB Server 10.2.2) WL#7990 stopped writing the field for other + than page 0 of the system tablespace. + + Starting from MariaDB 10.1 the field has been repurposed for + encryption key_version. + + Starting with MySQL 5.7 (and MariaDB Server 10.2), the + field has been repurposed for SPATIAL INDEX pages for + FIL_RTREE_SPLIT_SEQ_NUM. + + Note that FIL_PAGE_FILE_FLUSH_LSN is not included in the InnoDB page + checksum. + + Thus, FIL_PAGE_FILE_FLUSH_LSN could contain any value. While the + field would usually be 0 for pages that are not encrypted, we cannot + assume that a nonzero value means that the page is encrypted. + Therefore we must validate the page both as encrypted and unencrypted + when FIL_PAGE_FILE_FLUSH_LSN does not contain 0. */ + ulint checksum1 = mach_read_from_4( page + FIL_PAGE_SPACE_OR_CHKSUM);