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
4 changes: 4 additions & 0 deletions mysql-test/suite/rpl/t/rpl_table_map_log_event_overflow.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
!include ../my.cnf

[mysqld.1]
binlog_row_metadata=FULL
185 changes: 185 additions & 0 deletions mysql-test/suite/rpl/t/rpl_table_map_log_event_overflow.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
--source include/have_debug.inc
--source include/have_binlog_format_row.inc
--source include/master-slave.inc

--echo #
--echo # MDEV-39689: Slave Overflow on Malformed Table_map_log_event
--echo #
--echo # All tests verify that corrupted optional metadata lengths in
--echo # parse_* functions are handled gracefully. The slave should fall
--echo # back to positional column mapping and replicate successfully.
--echo #

--connection slave
CALL mtr.add_suppression("Error in Log_event::read_log_event.*Found invalid event");
CALL mtr.add_suppression("Relay log read failure");

--echo #
--echo # Test 1: parse_column_name - corrupted column name length
--echo #

--connection master
CREATE TABLE t1 (c1 INT PRIMARY KEY);
SET @@SESSION.debug_dbug="+d,corrupt_table_map_column_name_length";
INSERT INTO t1 VALUES (1);
SET @@SESSION.debug_dbug= "";
--sync_slave_with_master
SELECT * FROM t1;

--connection master
DROP TABLE t1;
--sync_slave_with_master

--echo #
--echo # Test 2: parse_default_charset - corrupted default charset length
--echo #

--connection master
CREATE TABLE t1 (c1 VARCHAR(10) CHARSET latin1, c2 VARCHAR(10) CHARSET latin1, c3 VARCHAR(10) CHARSET utf8mb4);
SET @@SESSION.debug_dbug="+d,corrupt_table_map_default_charset_length";
INSERT INTO t1 VALUES ('a', 'b', 'c');
SET @@SESSION.debug_dbug= "";
--sync_slave_with_master
SELECT * FROM t1;

--connection master
DROP TABLE t1;
--sync_slave_with_master

--echo #
--echo # Test 3: parse_column_charset - corrupted column charset length
--echo #

--connection master
CREATE TABLE t1 (c1 VARCHAR(10) CHARSET latin1);
SET @@SESSION.debug_dbug="+d,corrupt_table_map_column_charset_length";
INSERT INTO t1 VALUES ('a');
SET @@SESSION.debug_dbug= "";
--sync_slave_with_master
SELECT * FROM t1;

--connection master
DROP TABLE t1;
--sync_slave_with_master

--echo #
--echo # Test 4: parse_set_str_value - corrupted SET string value length
--echo #

--connection master
CREATE TABLE t1 (c1 SET('a','b','c'));
SET @@SESSION.debug_dbug="+d,corrupt_table_map_set_str_value_length";
INSERT INTO t1 VALUES ('a');
SET @@SESSION.debug_dbug= "";
--sync_slave_with_master
SELECT * FROM t1;

--connection master
DROP TABLE t1;
--sync_slave_with_master

--echo #
--echo # Test 5: parse_geometry_type - corrupted geometry type length
--echo #

--connection master
CREATE TABLE t1 (c1 GEOMETRY NOT NULL);
SET @@SESSION.debug_dbug="+d,corrupt_table_map_geometry_type_length";
INSERT INTO t1 VALUES (PointFromText('POINT(0 0)'));
SET @@SESSION.debug_dbug= "";
--sync_slave_with_master
SELECT ST_AsText(c1) FROM t1;

--connection master
DROP TABLE t1;
--sync_slave_with_master

--echo #
--echo # Test 6: parse_simple_pk - corrupted simple primary key length
--echo #

--connection master
CREATE TABLE t1 (c1 INT PRIMARY KEY);
SET @@SESSION.debug_dbug="+d,corrupt_table_map_simple_pk_length";
INSERT INTO t1 VALUES (1);
SET @@SESSION.debug_dbug= "";
--sync_slave_with_master
SELECT * FROM t1;

--connection master
DROP TABLE t1;
--sync_slave_with_master

--echo #
--echo # Test 7: parse_pk_with_prefix - corrupted prefix primary key length
--echo #

--connection master
CREATE TABLE t1 (c1 VARCHAR(100), PRIMARY KEY(c1(10)));
SET @@SESSION.debug_dbug="+d,corrupt_table_map_pk_prefix_length";
INSERT INTO t1 VALUES ('hello');
SET @@SESSION.debug_dbug= "";
--sync_slave_with_master
SELECT * FROM t1;

--connection master
DROP TABLE t1;
--sync_slave_with_master

--echo #
--echo # Test 8: Table_map_log_event constructor - corrupted column count
--echo # (slave-side injection) - slave rejects event gracefully
--echo #

--connection slave
--source include/stop_slave.inc
SET @saved_dbug= @@GLOBAL.debug_dbug;
SET @@GLOBAL.debug_dbug="+d,corrupt_table_map_colcnt_read";
--source include/start_slave.inc

--connection master
CREATE TABLE t1 (c1 INT PRIMARY KEY);
INSERT INTO t1 VALUES (1);
--source include/save_master_gtid.inc

--connection slave
--let $slave_sql_errno= 1594
--source include/wait_for_slave_sql_error.inc
SET @@GLOBAL.debug_dbug= @saved_dbug;
--source include/stop_slave_io.inc
SET GLOBAL sql_slave_skip_counter= 1;
--source include/start_slave.inc

--connection master
DROP TABLE t1;
--sync_slave_with_master

--echo #
--echo # Test 9: Table_map_log_event constructor - corrupted field metadata size
--echo # (slave-side injection) - slave rejects event gracefully
--echo #

--connection slave
--source include/stop_slave.inc
SET @saved_dbug= @@GLOBAL.debug_dbug;
SET @@GLOBAL.debug_dbug="+d,corrupt_table_map_field_metadata_size_read";
--source include/start_slave.inc

--connection master
CREATE TABLE t1 (c1 INT PRIMARY KEY);
INSERT INTO t1 VALUES (1);
--source include/save_master_gtid.inc

--connection slave
--let $slave_sql_errno= 1594
--source include/wait_for_slave_sql_error.inc
SET @@GLOBAL.debug_dbug= @saved_dbug;
--source include/stop_slave_io.inc
SET GLOBAL sql_slave_skip_counter= 1;
--source include/start_slave.inc

--connection master
DROP TABLE t1;
--sync_slave_with_master

--source include/rpl_end.inc
84 changes: 74 additions & 10 deletions sql/log_event.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3733,6 +3733,8 @@ Table_map_log_event::Table_map_log_event(const uchar *buf, uint event_len,
uchar *ptr_after_colcnt= (uchar*) ptr_colcnt;
VALIDATE_BYTES_READ(ptr_after_colcnt, buf, event_len);
m_colcnt= net_field_length(&ptr_after_colcnt);
DBUG_EXECUTE_IF("corrupt_table_map_colcnt_read",
m_colcnt= (1 << 20););

DBUG_PRINT("info",("m_dblen: %lu off: %ld m_tbllen: %lu off: %ld m_colcnt: %lu off: %ld",
(ulong) m_dblen, (long) (ptr_dblen - vpart),
Expand All @@ -3751,20 +3753,44 @@ Table_map_log_event::Table_map_log_event(const uchar *buf, uint event_len,
/* Copy the different parts into their memory */
strncpy(const_cast<char*>(m_dbnam), (const char*)ptr_dblen + 1, m_dblen + 1);
strncpy(const_cast<char*>(m_tblnam), (const char*)ptr_tbllen + 1, m_tbllen + 1);
if (unlikely(ptr_after_colcnt + m_colcnt > buf + event_len))
{
my_free(m_memory);
m_memory= NULL;
DBUG_VOID_RETURN;
}
memcpy(m_coltype, ptr_after_colcnt, m_colcnt);

ptr_after_colcnt= ptr_after_colcnt + m_colcnt;
VALIDATE_BYTES_READ(ptr_after_colcnt, buf, event_len);
m_field_metadata_size= net_field_length(&ptr_after_colcnt);
DBUG_EXECUTE_IF("corrupt_table_map_field_metadata_size_read",
m_field_metadata_size= (1 << 20););
if (m_field_metadata_size <= (m_colcnt * 2))
{
uint num_null_bytes= (m_colcnt + 7) / 8;
m_meta_memory= (uchar *)my_multi_malloc(PSI_INSTRUMENT_ME, MYF(MY_WME),
&m_null_bits, num_null_bytes,
&m_field_metadata, m_field_metadata_size,
NULL);
if (unlikely(ptr_after_colcnt + m_field_metadata_size > buf + event_len))
{
my_free(m_meta_memory);
m_meta_memory= NULL;
my_free(m_memory);
m_memory= NULL;
DBUG_VOID_RETURN;
}
memcpy(m_field_metadata, ptr_after_colcnt, m_field_metadata_size);
ptr_after_colcnt= (uchar*)ptr_after_colcnt + m_field_metadata_size;
if (unlikely(ptr_after_colcnt + num_null_bytes > buf + event_len))
{
my_free(m_meta_memory);
m_meta_memory= NULL;
my_free(m_memory);
m_memory= NULL;
DBUG_VOID_RETURN;
}
memcpy(m_null_bits, ptr_after_colcnt, num_null_bytes);
ptr_after_colcnt= (unsigned char*)ptr_after_colcnt + num_null_bytes;
}
Expand Down Expand Up @@ -3837,12 +3863,19 @@ static void parse_default_charset(Table_map_log_event::Optional_metadata_fields:
unsigned char *field, unsigned int length)
{
unsigned char* p= field;
unsigned char* end= field + length;

default_charset.default_charset= net_field_length(&p);
while (p < field + length)
if (unlikely(p > end))
return;
while (p < end)
{
unsigned int col_index= net_field_length(&p);
if (unlikely(p > end))
return;
unsigned int col_charset= net_field_length(&p);
if (unlikely(p > end))
return;

default_charset.charset_pairs.push_back(std::make_pair(col_index,
col_charset));
Expand All @@ -3860,9 +3893,15 @@ static void parse_column_charset(std::vector<unsigned int> &vec,
unsigned char *field, unsigned int length)
{
unsigned char* p= field;
unsigned char* end= field + length;

while (p < field + length)
vec.push_back(net_field_length(&p));
while (p < end)
{
unsigned int charset= net_field_length(&p);
if (unlikely(p > end))
return;
vec.push_back(charset);
}
}

/**
Expand All @@ -3876,10 +3915,13 @@ static void parse_column_name(std::vector<std::string> &vec,
unsigned char *field, unsigned int length)
{
unsigned char* p= field;
unsigned char* end= field + length;

while (p < field + length)
while (p < end)
{
unsigned len= net_field_length(&p);
if (unlikely(p + len > end))
return;
vec.push_back(std::string(reinterpret_cast<char *>(p), len));
p+= len;
}
Expand All @@ -3900,15 +3942,20 @@ static void parse_set_str_value(std::vector<Table_map_log_event::
unsigned char *field, unsigned int length)
{
unsigned char* p= field;
unsigned char* end= field + length;

while (p < field + length)
while (p < end)
{
unsigned int count= net_field_length(&p);
if (unlikely(p > end))
return;

vec.push_back(std::vector<std::string>());
for (unsigned int i= 0; i < count; i++)
{
unsigned len1= net_field_length(&p);
if (unlikely(p + len1 > end))
return;
vec.back().push_back(std::string(reinterpret_cast<char *>(p), len1));
p+= len1;
}
Expand All @@ -3926,9 +3973,15 @@ static void parse_geometry_type(std::vector<unsigned int> &vec,
unsigned char *field, unsigned int length)
{
unsigned char* p= field;
unsigned char* end= field + length;

while (p < field + length)
vec.push_back(net_field_length(&p));
while (p < end)
{
unsigned int geom_type= net_field_length(&p);
if (unlikely(p > end))
return;
vec.push_back(geom_type);
}
}

/**
Expand All @@ -3946,9 +3999,15 @@ static void parse_simple_pk(std::vector<Table_map_log_event::
unsigned char *field, unsigned int length)
{
unsigned char* p= field;
unsigned char* end= field + length;

while (p < field + length)
vec.push_back(std::make_pair(net_field_length(&p), 0));
while (p < end)
{
unsigned int col_index= net_field_length(&p);
if (unlikely(p > end))
return;
vec.push_back(std::make_pair(col_index, (unsigned int) 0));
}
}

/**
Expand All @@ -3966,11 +4025,16 @@ static void parse_pk_with_prefix(std::vector<Table_map_log_event::
unsigned char *field, unsigned int length)
{
unsigned char* p= field;
unsigned char* end= field + length;

while (p < field + length)
while (p < end)
{
unsigned int col_index= net_field_length(&p);
if (unlikely(p > end))
return;
unsigned int col_prefix= net_field_length(&p);
if (unlikely(p > end))
return;
vec.push_back(std::make_pair(col_index, col_prefix));
}
}
Expand Down
Loading