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]

Reply via email to