Validate the chunk_len in the binlog chunk reader, so we don't try to read data outside of the page.
Signed-off-by: Kristian Nielsen <[email protected]> --- client/mysqlbinlog-engine.cc | 18 ++++++++++------ .../corrupt-chunk-len_binlog-000000.ibb | Bin 0 -> 32768 bytes .../corrupt-chunk-len_binlog-000001.ibb | Bin 0 -> 32768 bytes .../binlog_in_engine/binlog_restart.result | 6 ++++++ .../binlog_in_engine/binlog_restart.test | 14 ++++++++++++ .../suite/binlog_in_engine/mysqlbinlog.result | 6 ++++++ .../suite/binlog_in_engine/mysqlbinlog.test | 3 +++ storage/innobase/fsp/fsp_binlog.cc | 20 +++++++++++------- storage/innobase/handler/innodb_binlog.cc | 2 -- 9 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 mysql-test/std_data/corrupt-chunk-len_binlog-000000.ibb create mode 100644 mysql-test/std_data/corrupt-chunk-len_binlog-000001.ibb diff --git a/client/mysqlbinlog-engine.cc b/client/mysqlbinlog-engine.cc index cbcd8a01441..96421b35f29 100644 --- a/client/mysqlbinlog-engine.cc +++ b/client/mysqlbinlog-engine.cc @@ -383,6 +383,7 @@ chunk_reader_mysqlbinlog::read_data(uchar *buffer, int max_len, bool multipage) { uint32_t size; int sofar= 0; + uint32_t chunk_len; read_more_data: if (max_len == 0) @@ -436,14 +437,19 @@ chunk_reader_mysqlbinlog::read_data(uchar *buffer, int max_len, bool multipage) FSP_BINLOG_FLAG_BIT_CONT bit set, and the final one must have the FSP_BINLOG_FLAG_BIT_LAST bit set. */ + chunk_len= page_buffer[s.in_page_offset + 1] | + ((uint32_t)page_buffer[s.in_page_offset + 2] << 8); + if (chunk_len + (3 + BINLOG_PAGE_DATA_END) > + binlog_page_size - s.in_page_offset) + return read_error_corruption(s.file_no, s.page_no, + "Invalid chunk length"); + s.chunk_len= chunk_len; if (!s.in_record) { if (type & FSP_BINLOG_FLAG_CONT && !s.skip_current) { if (skipping_partial) { - s.chunk_len= page_buffer[s.in_page_offset + 1] | - ((uint32_t)page_buffer[s.in_page_offset + 2] << 8); s.skip_current= true; goto skip_chunk; } @@ -463,8 +469,6 @@ chunk_reader_mysqlbinlog::read_data(uchar *buffer, int max_len, bool multipage) if (((uint64_t)1 << (type & FSP_BINLOG_TYPE_MASK)) & ALLOWED_NESTED_RECORDS) { - s.chunk_len= page_buffer[s.in_page_offset + 1] | - ((uint32_t)page_buffer[s.in_page_offset + 2] << 8); goto skip_chunk; } /* Chunk type changed in the middle. */ @@ -482,12 +486,12 @@ chunk_reader_mysqlbinlog::read_data(uchar *buffer, int max_len, bool multipage) s.skip_current= false; s.chunk_type= type; s.in_record= true; - s.chunk_len= page_buffer[s.in_page_offset + 1] | - ((uint32_t)page_buffer[s.in_page_offset + 2] << 8); s.chunk_read_offset= 0; } /* Now we have a chunk available to read data from. */ + DBUG_ASSERT(s.in_page_offset + s.chunk_len + 3 <= + binlog_page_size - BINLOG_PAGE_DATA_END); DBUG_ASSERT(s.chunk_read_offset < s.chunk_len); if (s.skip_current && (s.chunk_read_offset > 0 || (s.chunk_type & FSP_BINLOG_FLAG_CONT))) @@ -517,6 +521,8 @@ chunk_reader_mysqlbinlog::read_data(uchar *buffer, int max_len, bool multipage) /* We have read all of the chunk. Move to next chunk or end of the record. */ skip_chunk: + DBUG_ASSERT(s.in_page_offset + s.chunk_len + 3 <= + binlog_page_size - BINLOG_PAGE_DATA_END); s.in_page_offset+= 3 + s.chunk_len; s.chunk_len= 0; s.chunk_read_offset= 0; diff --git a/mysql-test/std_data/corrupt-chunk-len_binlog-000000.ibb b/mysql-test/std_data/corrupt-chunk-len_binlog-000000.ibb new file mode 100644 index 0000000000000000000000000000000000000000..f6dd5cda1354f2efc652df46900ee3d831012566 GIT binary patch literal 32768 zcmeI)%}PQ+6ae7SB-|wJTC^DmBB4z|n;=5-5@t!51VW2`1zXrcAD|wkcD+Dv6YYBl z>)tf5-az^0a+x#doW;!d0B1B>%9TP0x%8i2%acR$F%F8;#Wen!tbb|884}pOe7>2X z>$)ld0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1m;%Yedp}Hnopls>2||;X*IlLQvlW{?=AkB2~Zwqv#xyB z^*HGYp^!$ejn7`Xul*!*X+5^HaB*QUye$+%_C4+X6hj!^4Tj;Ml~meEY*(tsNgQs) zjjLF1w&Q85eo|?5Vk7Bn#_KFqj!E;d-c0uD{r*F(T07eRojhPCK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ PfB*pk^C9qdvC{hnfhIXB literal 0 HcmV?d00001 diff --git a/mysql-test/std_data/corrupt-chunk-len_binlog-000001.ibb b/mysql-test/std_data/corrupt-chunk-len_binlog-000001.ibb new file mode 100644 index 0000000000000000000000000000000000000000..8ee531f2b2b1059f27f259e365252b9992a71dcb GIT binary patch literal 32768 zcmeI)%}N4M7{>9}83h*=L5m6(9XDkkL{kJUiWv1ofoZ7K+MrFF7I_2RNpu5ULF+)e zg;p)P38{Sb%osWZE{p%baL$~M;eCFd?fCgQothDmRPWX+YG((J^0G1*$-3ug{>xo% znC1@(+~1a#{$SaZi~s@%Ab<b@2q1s}0tg_000IagfB*srAb<b@2q1s}0tg_000Iag zfB*srAb<b@2q1s}0tg_000IagfB*srAb<b@2q1s}0toy&fw$epO)(?APy4gk@Ll9$ zCMS>jf&a3LnicOy=&3<pS3T&e!+7qb+OYMeIjJ^lrX84zuySr2rVwnHt<?=v*mmuX zwz_>zx39(9^Zi~*((UWJws`G8Jl_{jSI+Coy|I;^-_;4dx>1Q;e=yLGF{{%#Hy{$a zUf4-Y8ST_nqbWDzmz<xK!=}}M4cjzV2Z6afwsmV-2f<{a7PPpn_`UglFQuV}sdS0n z`sR@^MYl72P7ITF)7p%NS&!$+wqko>j_S2jO%x^Bo-E0tmKdEDkztbfS4pV9<cbJi z6YI~!HICOmM627gN?5XEXRdd~b|ySyzx*80!RVhYeMpE?cJ-E@_M<<=phW-y1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** Z5I_I{1Q0*~0R#|0009Ih6!`F4op0)a(V74N literal 0 HcmV?d00001 diff --git a/mysql-test/suite/binlog_in_engine/binlog_restart.result b/mysql-test/suite/binlog_in_engine/binlog_restart.result index 3e2be45330b..ca681e561d9 100644 --- a/mysql-test/suite/binlog_in_engine/binlog_restart.result +++ b/mysql-test/suite/binlog_in_engine/binlog_restart.result @@ -19,3 +19,9 @@ INSERT INTO t1 VALUES (7, 0, REPEAT('&', 243700)); INSERT INTO t1 VALUES (8, 0, REPEAT('&', $length)); # restart DROP TABLE t1; +*** Test restarting on malicious binlog file with invalid chunk_len +RESET MASTER; +# restart +SHOW BINLOG EVENTS IN 'binlog-000000.ibb'; +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: error reading event data +CALL mtr.add_suppression("InnoDB: Corrupt binlog found on page 1 in binlog number 0: Invalid chunk length"); diff --git a/mysql-test/suite/binlog_in_engine/binlog_restart.test b/mysql-test/suite/binlog_in_engine/binlog_restart.test index 5ff4ae9337c..5bd52270792 100644 --- a/mysql-test/suite/binlog_in_engine/binlog_restart.test +++ b/mysql-test/suite/binlog_in_engine/binlog_restart.test @@ -1,6 +1,8 @@ --source include/have_binlog_format_row.inc --source include/have_innodb_binlog.inc +--let $datadir= `SELECT @@datadir` + CREATE TABLE t1(a INT PRIMARY KEY, b INT, c LONGBLOB) ENGINE=InnoDB; --echo *** Test restarting the server on an existing binlog @@ -61,3 +63,15 @@ evalp INSERT INTO t1 VALUES (8, 0, REPEAT('&', $length)); --source include/restart_mysqld.inc DROP TABLE t1; + +--echo *** Test restarting on malicious binlog file with invalid chunk_len +RESET MASTER; +--source include/shutdown_mysqld.inc +--remove_file $datadir/binlog-000000.ibb +--remove_file $datadir/binlog-000001.ibb +--copy_file std_data/corrupt-chunk-len_binlog-000000.ibb $datadir/binlog-000000.ibb +--copy_file std_data/corrupt-chunk-len_binlog-000001.ibb $datadir/binlog-000001.ibb +--source include/start_mysqld.inc +--error ER_ERROR_WHEN_EXECUTING_COMMAND +SHOW BINLOG EVENTS IN 'binlog-000000.ibb'; +CALL mtr.add_suppression("InnoDB: Corrupt binlog found on page 1 in binlog number 0: Invalid chunk length"); diff --git a/mysql-test/suite/binlog_in_engine/mysqlbinlog.result b/mysql-test/suite/binlog_in_engine/mysqlbinlog.result index f4d93d2e50f..cf4c1d511eb 100644 --- a/mysql-test/suite/binlog_in_engine/mysqlbinlog.result +++ b/mysql-test/suite/binlog_in_engine/mysqlbinlog.result @@ -109,4 +109,10 @@ DROP TABLE t1; /*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; DELIMITER /*!*/; ERROR: Invalid/unsupported page size in InnoDB binlog file, cannot read +/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/; +/*!40019 SET @@session.max_delayed_threads=0*/; +/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; +DELIMITER /*!*/; +ERROR: Corrupt InnoDB binlog found on page 1 in binlog number 0: Invalid chunk length +ERROR: Could not read entry at offset 4: Error in log format or read error. # End of 12.3 tests diff --git a/mysql-test/suite/binlog_in_engine/mysqlbinlog.test b/mysql-test/suite/binlog_in_engine/mysqlbinlog.test index 674d7647cf2..2aae0c8d818 100644 --- a/mysql-test/suite/binlog_in_engine/mysqlbinlog.test +++ b/mysql-test/suite/binlog_in_engine/mysqlbinlog.test @@ -91,4 +91,7 @@ EOF --exec $MYSQL_BINLOG --short-form $MYSQL_TMP_DIR/bad-binlog.ibb 2>&1 --remove_file $MYSQL_TMP_DIR/bad-binlog.ibb +--error 1 +--exec $MYSQL_BINLOG --short-form $MYSQL_TEST_DIR/std_data/corrupt-chunk-len_binlog-000000.ibb 2>&1 + --echo # End of 12.3 tests diff --git a/storage/innobase/fsp/fsp_binlog.cc b/storage/innobase/fsp/fsp_binlog.cc index 30ffede110d..70c4e63868e 100644 --- a/storage/innobase/fsp/fsp_binlog.cc +++ b/storage/innobase/fsp/fsp_binlog.cc @@ -2061,6 +2061,7 @@ binlog_chunk_reader::read_data(byte *buffer, int max_len, bool multipage) { uint32_t size; int sofar= 0; + uint32_t chunk_len; read_more_data: if (max_len == 0) @@ -2123,14 +2124,19 @@ binlog_chunk_reader::read_data(byte *buffer, int max_len, bool multipage) FSP_BINLOG_FLAG_BIT_CONT bit set, and the final one must have the FSP_BINLOG_FLAG_BIT_LAST bit set. */ + chunk_len= page_ptr[s.in_page_offset + 1] | + ((uint32_t)page_ptr[s.in_page_offset + 2] << 8); + if (UNIV_UNLIKELY(chunk_len + (3 + BINLOG_PAGE_DATA_END) > + ibb_page_size - s.in_page_offset)) + return read_error_corruption(s.file_no, s.page_no, + "Invalid chunk length"); + s.chunk_len= chunk_len; if (!s.in_record) { if (UNIV_UNLIKELY(type & FSP_BINLOG_FLAG_CONT) && !s.skip_current) { if (skipping_partial) { - s.chunk_len= page_ptr[s.in_page_offset + 1] | - ((uint32_t)page_ptr[s.in_page_offset + 2] << 8); s.skip_current= true; goto skip_chunk; } @@ -2149,11 +2155,7 @@ binlog_chunk_reader::read_data(byte *buffer, int max_len, bool multipage) */ if (((uint64_t)1 << (type & FSP_BINLOG_TYPE_MASK)) & ALLOWED_NESTED_RECORDS) - { - s.chunk_len= page_ptr[s.in_page_offset + 1] | - ((uint32_t)page_ptr[s.in_page_offset + 2] << 8); goto skip_chunk; - } /* Chunk type changed in the middle. */ return read_error_corruption(s.file_no, s.page_no, "Binlog record missing " "end chunk"); @@ -2170,12 +2172,12 @@ binlog_chunk_reader::read_data(byte *buffer, int max_len, bool multipage) s.chunk_type= type; s.in_record= true; s.rec_start_file_no= s.file_no; - s.chunk_len= page_ptr[s.in_page_offset + 1] | - ((uint32_t)page_ptr[s.in_page_offset + 2] << 8); s.chunk_read_offset= 0; } /* Now we have a chunk available to read data from. */ + DBUG_ASSERT(s.in_page_offset + s.chunk_len + 3 <= + ibb_page_size - BINLOG_PAGE_DATA_END); ut_ad(s.chunk_read_offset < s.chunk_len); if (s.skip_current && (s.chunk_read_offset > 0 || (s.chunk_type & FSP_BINLOG_FLAG_CONT))) @@ -2205,6 +2207,8 @@ binlog_chunk_reader::read_data(byte *buffer, int max_len, bool multipage) /* We have read all of the chunk. Move to next chunk or end of the record. */ skip_chunk: + DBUG_ASSERT(s.in_page_offset + s.chunk_len + 3 <= + ibb_page_size - BINLOG_PAGE_DATA_END); s.in_page_offset+= 3 + s.chunk_len; s.chunk_len= 0; s.chunk_read_offset= 0; diff --git a/storage/innobase/handler/innodb_binlog.cc b/storage/innobase/handler/innodb_binlog.cc index 667768941a0..b36d00ddb66 100644 --- a/storage/innobase/handler/innodb_binlog.cc +++ b/storage/innobase/handler/innodb_binlog.cc @@ -2034,8 +2034,6 @@ innodb_binlog_discover() &header); if (res < 0) { file_no= binlog_files.last_file_no; - if (ibb_record_in_file_hash(file_no, ~(uint64_t)0, ~(uint64_t)0)) - return -1; binlog_discover_init(file_no, innodb_binlog_state_interval); binlog_cur_page_no= page_no; binlog_cur_page_offset= pos_in_page; -- 2.47.3 _______________________________________________ commits mailing list -- [email protected] To unsubscribe send an email to [email protected]
