diff --git a/mysql-test/main/binlog_invalid_row_v2_tag.result b/mysql-test/main/binlog_invalid_row_v2_tag.result new file mode 100644 index 0000000000000..63f13f71399a1 --- /dev/null +++ b/mysql-test/main/binlog_invalid_row_v2_tag.result @@ -0,0 +1 @@ +FOUND 3 /[Uu]nknown [Ee]vent/ in invalid_row_v2_tag.sql diff --git a/mysql-test/main/binlog_invalid_row_v2_tag.test b/mysql-test/main/binlog_invalid_row_v2_tag.test new file mode 100644 index 0000000000000..283e27ff66a14 --- /dev/null +++ b/mysql-test/main/binlog_invalid_row_v2_tag.test @@ -0,0 +1,14 @@ +# MDEV-39485 Heap-buffer-overflow upon read in `Rows_log_event` constructor +# +# This binlog file contains a normal Format Description +# Event followed by 3 malformed v2 Write Rows Events: +# 1. A tag followed by no data +# 2. An undersized tagged data followed by an unrecognized tag +# 3. A tag followed by a length longer than the entire event + +--source include/not_embedded.inc + +--let SEARCH_PATTERN= [Uu]nknown [Ee]vent +--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/invalid_row_v2_tag.sql +--exec $MYSQL_BINLOG --force-read --verbose std_data/binlog_invalid_row_v2_tag.001 > $SEARCH_FILE +--source include/search_pattern_in_file.inc diff --git a/mysql-test/std_data/binlog_invalid_row_v2_tag.001 b/mysql-test/std_data/binlog_invalid_row_v2_tag.001 new file mode 100644 index 0000000000000..4b558372ea550 Binary files /dev/null and b/mysql-test/std_data/binlog_invalid_row_v2_tag.001 differ diff --git a/mysql-test/suite/binlog/r/fdle_overflow.result b/mysql-test/suite/binlog/r/fdle_overflow.result new file mode 100644 index 0000000000000..3fde805de82c2 --- /dev/null +++ b/mysql-test/suite/binlog/r/fdle_overflow.result @@ -0,0 +1,10 @@ +SET @saved_dbug= @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug= +'+d,truncate_fde_post_header_len,truncate_fde_used_checksum_alg'; +FLUSH BINARY LOGS; +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_common_header_len'; +FLUSH BINARY LOGS; +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; diff --git a/mysql-test/suite/binlog/r/unknown_log_event.result b/mysql-test/suite/binlog/r/unknown_log_event.result new file mode 100644 index 0000000000000..b3bbc14a6de77 --- /dev/null +++ b/mysql-test/suite/binlog/r/unknown_log_event.result @@ -0,0 +1,8 @@ +CREATE TABLE t (a INT); +INSERT INTO t VALUES (0); +DROP TABLE t; +FLUSH BINARY LOGS; +#mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=#binlog_start --stop-position=#binlog_stop #binlog_file 2>&1 +FOUND 1 /CRC32 0x[[:xdigit:]]{8}.*\n# [Uu]nknown [Ee]vent/ in unknown_log_event.sql +#mariadb_binlog --debug=d,simulate_checksum_test_failure --stop-position=5 #binlog_file 2>&1 +FOUND 1 /CRC32 0x[[:xdigit:]]{8}.*\n# [Uu]nknown [Ee]vent/ in unknown_log_event.sql diff --git a/mysql-test/suite/binlog/t/fdle_overflow.test b/mysql-test/suite/binlog/t/fdle_overflow.test new file mode 100644 index 0000000000000..abaf143d5156c --- /dev/null +++ b/mysql-test/suite/binlog/t/fdle_overflow.test @@ -0,0 +1,38 @@ +--source include/have_debug.inc +--source include/have_binlog_format_mixed.inc # format-agnostic + +# OOB read on malformed `Format_description_log_event` +# +# Verify that Format Description Events with truncated contents fails gracefully +# rather than underflow the post-header count and lead to buffer over-read. + +SET @saved_dbug= @@GLOBAL.debug_dbug; + + +# MDEV-40366: `used_checksum_alg` + +SET @@GLOBAL.debug_dbug= + '+d,truncate_fde_post_header_len,truncate_fde_used_checksum_alg'; +FLUSH BINARY LOGS; + +--let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) +--disable_query_log + --error ER_ERROR_WHEN_EXECUTING_COMMAND + --eval SHOW BINLOG EVENTS IN '$binlog_file' +--enable_query_log + + +# MDEV-40365: `common_header_len` & `post_header_len` + +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_common_header_len'; +FLUSH BINARY LOGS; + +--let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) +--disable_query_log + --error ER_ERROR_WHEN_EXECUTING_COMMAND + --eval SHOW BINLOG EVENTS IN '$binlog_file' +--enable_query_log + +# Clean-up +SET @@GLOBAL.debug_dbug= @saved_dbug; +RESET MASTER; diff --git a/mysql-test/suite/binlog/t/unknown_log_event.test b/mysql-test/suite/binlog/t/unknown_log_event.test new file mode 100644 index 0000000000000..267d107816b45 --- /dev/null +++ b/mysql-test/suite/binlog/t/unknown_log_event.test @@ -0,0 +1,37 @@ +# MDEV-40674: Test `mariadb-binlog --force`'s Unknown event output +# +# Also: MDEV-40542 +# MSAN use-of-uninitialized-value on Unknown_log_event::read_checksum_alg + +--source include/have_debug.inc +--source include/have_binlog_format_row.inc # Testing with Table Map event + +# Setup +CREATE TABLE t (a INT); +--let $binlog_file= query_get_value(SHOW BINLOG STATUS, File, 1) +--let $binlog_start= query_get_value(SHOW BINLOG STATUS, Position, 1) + +# Generate an invalid Table Map event followed by an otherwise-valid Rows Event +INSERT INTO t VALUES (0); + +--let $binlog_stop= query_get_value(SHOW BINLOG STATUS, Position, 1) +DROP TABLE t; +FLUSH BINARY LOGS; + + +--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/unknown_log_event.sql +--let SEARCH_PATTERN= CRC32 0x[[:xdigit:]]{8}.*\\n# [Uu]nknown [Ee]vent +--let $mariadb_binlog= $MYSQL_BINLOG --result-file=$SEARCH_FILE --verify-binlog-checksum --force-read +--let $binlog_file= `SELECT CONCAT(@@datadir, '/$binlog_file')` + +--echo #mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=#binlog_start --stop-position=#binlog_stop #binlog_file 2>&1 +--exec $mariadb_binlog --debug=d,corrupt_table_map_colcnt_read --start-position=$binlog_start --stop-position=$binlog_stop $binlog_file 2>&1 +--source include/search_pattern_in_file.inc + +# Test checksum failure +# +# Only test the first event since the rest of the binlog is unreadable +# without a valid Format Description, which is typically the first event. +--echo #mariadb_binlog --debug=d,simulate_checksum_test_failure --stop-position=5 #binlog_file 2>&1 +--exec $mariadb_binlog --debug=d,simulate_checksum_test_failure --stop-position=5 $binlog_file 2>&1 +--source include/search_pattern_in_file.inc diff --git a/mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result b/mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result new file mode 100644 index 0000000000000..0529ff7ca4f64 --- /dev/null +++ b/mysql-test/suite/rpl/r/rpl_rotate_ev_overflow.result @@ -0,0 +1,19 @@ +SET @saved_dbug= @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_post_header_len'; +include/master-slave.inc +[connection master] +FLUSH BINARY LOGS; +CALL mtr.add_suppression('Found invalid event in binary log'); +ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error +connection slave; +START SLAVE IO_THREAD; +CALL mtr.add_suppression('Slave I/O: Relay log write failure'); +include/wait_for_slave_io_error.inc [errno=1595] +connection master; +SET @@GLOBAL.debug_dbug= @saved_dbug; +include/reset_master.inc +connection slave; +CHANGE MASTER TO master_use_gtid=SLAVE_POS; +SET @@GLOBAL.gtid_slave_pos=''; +include/start_slave.inc +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test b/mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test new file mode 100644 index 0000000000000..f7782a311d662 --- /dev/null +++ b/mysql-test/suite/rpl/t/rpl_rotate_ev_overflow.test @@ -0,0 +1,51 @@ +# MDEV-40647 OOB read in IO Thread if the FDEv does not support Rotate Events + +--source include/have_debug.inc +--source include/have_binlog_format_mixed.inc # should be format-agnostic + +SET @saved_dbug= @@GLOBAL.debug_dbug; +SET @@GLOBAL.debug_dbug= '+d,truncate_fde_post_header_len'; +--let $rpl_skip_start_slave= 1 +# This setup will also RESET MASTER ... +--source include/master-slave.inc +# ... which means the binlog file is consistent. +--let $binlog_file= master-bin.000001 + +# Use a specific binlog position to avoid the +# test failing early from other metadata events +--let $binlog_start= query_get_value(SHOW BINLOG STATUS, Position, 1) +# Generate the Rotate Event +FLUSH BINARY LOGS; + + +# Control: SHOW BINLOG EVENTS fails as expected. +CALL mtr.add_suppression('Found invalid event in binary log'); +--disable_query_log + --error ER_ERROR_WHEN_EXECUTING_COMMAND + --eval SHOW BINLOG EVENTS IN '$binlog_file' FROM $binlog_start +--enable_query_log + +--connection slave +--disable_query_log + eval CHANGE MASTER TO master_use_gtid=NO, + master_log_file='$binlog_file', master_log_pos=$binlog_start; +--enable_query_log +START SLAVE IO_THREAD; +CALL mtr.add_suppression('Slave I/O: Relay log write failure'); +# ER_SLAVE_RELAY_LOG_WRITE_FAILURE +--let $slave_io_errno= 1595 +# Experiment: The IO thread should fail. +--source include/wait_for_slave_io_error.inc + +# Clean-up +--connection master +SET @@GLOBAL.debug_dbug= @saved_dbug; +--source include/reset_master.inc + +--connection slave +CHANGE MASTER TO master_use_gtid=SLAVE_POS; # restore the default +--disable_warnings + SET @@GLOBAL.gtid_slave_pos=''; # for good measure +--enable_warnings +--source include/start_slave.inc +--source include/rpl_end.inc diff --git a/sql/log_event.cc b/sql/log_event.cc index 8e6de277a080e..00134a6fcef2f 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -981,7 +981,7 @@ Log_event* Log_event::read_log_event(const uchar *buf, size_t event_len, my_bool crc_check, my_bool print_errors) { - Log_event* ev; + Log_event* ev= nullptr; enum_binlog_checksum_alg alg; DBUG_ENTER("Log_event::read_log_event(char*,...)"); DBUG_ASSERT(fdle != 0); @@ -1000,15 +1000,33 @@ Log_event* Log_event::read_log_event(const uchar *buf, size_t event_len, } uint event_type= buf[EVENT_TYPE_OFFSET]; + switch (event_type) { + case FORMAT_DESCRIPTION_EVENT: + // If event is FD the descriptor is in it. + if (unlikely(get_checksum_alg(buf, event_len, &alg))) + { +#ifdef MYSQL_CLIENT + if (force_opt) + { + event_len-= BINLOG_CHECKSUM_LEN; + ev= new Unknown_log_event(buf, fdle); + goto exit; + } +#endif + *error= "Found invalid event in binary log"; + DBUG_RETURN(nullptr); + } + break; + case START_EVENT_V3: // all following START events in the current file are without checksum - if (event_type == START_EVENT_V3) (const_cast< Format_description_log_event *>(fdle))->used_checksum_alg= BINLOG_CHECKSUM_ALG_OFF; + // fall-through + default: /* CRC verification by SQL and Show-Binlog-Events master side. The caller has to provide @fdle->checksum_alg to be the last seen FD's (A) descriptor. - If event is FD the descriptor is in it. Notice, FD of the binlog can be only in one instance and therefore Show-Binlog-Events executing master side thread needs just to know the only FD's (A) value - whereas RL can contain more. @@ -1023,11 +1041,9 @@ Log_event* Log_event::read_log_event(const uchar *buf, size_t event_len, Notice, a pre-checksum FD version forces alg := BINLOG_CHECKSUM_ALG_UNDEF. */ - alg= (event_type != FORMAT_DESCRIPTION_EVENT) ? - fdle->used_checksum_alg : get_checksum_alg(buf, event_len); + alg= fdle->used_checksum_alg; // Emulate the corruption during reading an event DBUG_EXECUTE_IF("corrupt_read_log_event_char", - if (event_type != FORMAT_DESCRIPTION_EVENT) { uchar *debug_event_buf_c= const_cast(buf); int debug_cor_pos= rand() % (event_len - BINLOG_CHECKSUM_LEN); @@ -1036,23 +1052,23 @@ Log_event* Log_event::read_log_event(const uchar *buf, size_t event_len, DBUG_SET("-d,corrupt_read_log_event_char"); } ); + } if (crc_check && event_checksum_test(const_cast(buf), event_len, alg)) { #ifdef MYSQL_CLIENT - *error= "Event crc check failed! Most likely there is event corruption."; if (force_opt) { + event_len-= BINLOG_CHECKSUM_LEN; ev= new Unknown_log_event(buf, fdle); - DBUG_RETURN(ev); + goto exit; } - else - DBUG_RETURN(NULL); + *error= "Event crc check failed! Most likely there is event corruption."; #else *error= ER_THD_OR_DEFAULT(current_thd, ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE); if (print_errors) sql_print_error("%s", *error); - DBUG_RETURN(NULL); #endif + DBUG_RETURN(NULL); } if (event_type > fdle->number_of_event_types && @@ -1097,14 +1113,15 @@ Log_event* Log_event::read_log_event(const uchar *buf, size_t event_len, ev= Log_event::read_log_event_no_checksum(buf, event_len, error, fdle); } +#ifdef MYSQL_CLIENT +exit: if (ev) { -#ifdef MYSQL_CLIENT ev->read_checksum_alg= alg; if (alg != BINLOG_CHECKSUM_ALG_OFF && alg != BINLOG_CHECKSUM_ALG_UNDEF) ev->read_checksum_value= uint4korr(buf + (event_len)); -#endif } +#endif DBUG_RETURN(ev); } @@ -2325,7 +2342,8 @@ Format_description_log_event(const uchar *buf, uint event_len, { DBUG_ENTER("Format_description_log_event::Format_description_log_event(char*,...)"); used_checksum_alg= BINLOG_CHECKSUM_ALG_UNDEF; - if (event_len < LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET) + if (unlikely( + event_len < LOG_EVENT_MINIMAL_HEADER_LEN + ST_POST_HEADER_LEN_OFFSET)) { server_version[0]= 0; DBUG_VOID_RETURN; @@ -2338,32 +2356,36 @@ Format_description_log_event(const uchar *buf, uint event_len, created= uint4korr(buf+ST_CREATED_OFFSET); dont_set_created= 1; - if (server_version[0] == 0) + if (unlikely(server_version[0] == 0)) DBUG_VOID_RETURN; /* sanity check */ - if ((common_header_len=buf[ST_COMMON_HEADER_LEN_OFFSET]) < LOG_EVENT_MINIMAL_HEADER_LEN) + if (unlikely( + (common_header_len=buf[ST_COMMON_HEADER_LEN_OFFSET]) < LOG_EVENT_MINIMAL_HEADER_LEN)) DBUG_VOID_RETURN; /* sanity check */ number_of_event_types= - event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + ST_COMMON_HEADER_LEN_OFFSET + 1); + event_len - (LOG_EVENT_MINIMAL_HEADER_LEN + ST_POST_HEADER_LEN_OFFSET); DBUG_PRINT("info", ("common_header_len=%d number_of_event_types=%d", common_header_len, number_of_event_types)); /* If alloc fails, we'll detect it in is_valid() */ - post_header_len= (uint8*) my_memdup(PSI_INSTRUMENT_ME, - buf+ST_COMMON_HEADER_LEN_OFFSET+1, - number_of_event_types* - sizeof(*post_header_len), - MYF(0)); calc_server_version_split(); + buf+= ST_POST_HEADER_LEN_OFFSET; if (!is_version_before_checksum(&server_version_split)) { - /* the last bytes are the checksum alg desc and value (or value's room) */ + /* the last bytes are the checksum alg desc */ + if (unlikely(number_of_event_types < BINLOG_CHECKSUM_ALG_DESC_LEN)) + DBUG_VOID_RETURN; /* sanity check: But there are no last bytes. */ number_of_event_types -= BINLOG_CHECKSUM_ALG_DESC_LEN; - used_checksum_alg= (enum_binlog_checksum_alg)post_header_len[number_of_event_types]; + used_checksum_alg= (enum_binlog_checksum_alg)buf[number_of_event_types]; } else { used_checksum_alg= BINLOG_CHECKSUM_ALG_OFF; } + post_header_len= (uint8*) my_memdup(PSI_INSTRUMENT_ME, + buf, + number_of_event_types* + sizeof(*post_header_len), + MYF(0)); deduct_options_written_to_bin_log(); reset_crypto(); @@ -2477,35 +2499,50 @@ Format_description_log_event::is_version_before_checksum(const master_version_sp } /** - @param buf buffer holding serialized FD event - @param len netto (possible checksum is stripped off) length of the event buf - - @return the version-safe checksum alg descriptor where zero + @param buf buffer holding serialized FD event including the 4-byte checksum + @param len length of the event buf + @param alg output the version-safe checksum alg descriptor where zero designates no checksum, 255 - the orginator is checksum-unaware (effectively no checksum) and the actual [1-254] range alg descriptor. + @return whether this is an invalid FD event */ -enum_binlog_checksum_alg get_checksum_alg(const uchar *buf, size_t len) +bool get_checksum_alg(const uchar *buf, size_t len, + enum_binlog_checksum_alg *alg) { - enum_binlog_checksum_alg ret; + constexpr ptrdiff_t POST_HEADER_LEN_OFFSET= + LOG_EVENT_MINIMAL_HEADER_LEN + ST_POST_HEADER_LEN_OFFSET; char version[ST_SERVER_VER_LEN]; DBUG_ENTER("get_checksum_alg"); DBUG_ASSERT(buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT); + if (unlikely(len < POST_HEADER_LEN_OFFSET)) + DBUG_RETURN(true); memcpy(version, buf + LOG_EVENT_MINIMAL_HEADER_LEN + ST_SERVER_VER_OFFSET, ST_SERVER_VER_LEN); version[ST_SERVER_VER_LEN - 1]= 0; Format_description_log_event::master_version_split version_split(version); - ret= Format_description_log_event::is_version_before_checksum(&version_split) - ? BINLOG_CHECKSUM_ALG_UNDEF - : (enum_binlog_checksum_alg)buf[len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN]; - DBUG_ASSERT(ret == BINLOG_CHECKSUM_ALG_OFF || - ret == BINLOG_CHECKSUM_ALG_UNDEF || - ret == BINLOG_CHECKSUM_ALG_CRC32); - DBUG_RETURN(ret); + if (Format_description_log_event::is_version_before_checksum(&version_split)) + *alg= BINLOG_CHECKSUM_ALG_UNDEF; + else + { + /* + len >= POST_HEADER_LEN_OFFSET > + BINLOG_CHECKSUM_LEN + BINLOG_CHECKSUM_ALG_DESC_LEN + */ + size_t checksum_alg_offset= + len - BINLOG_CHECKSUM_LEN - BINLOG_CHECKSUM_ALG_DESC_LEN; + if (unlikely(checksum_alg_offset < POST_HEADER_LEN_OFFSET)) + DBUG_RETURN(true); + *alg= static_cast(buf[checksum_alg_offset]); + } + DBUG_ASSERT(*alg == BINLOG_CHECKSUM_ALG_OFF || + *alg == BINLOG_CHECKSUM_ALG_UNDEF || + *alg == BINLOG_CHECKSUM_ALG_CRC32); + DBUG_RETURN(false); } Start_encryption_log_event:: @@ -3296,9 +3333,17 @@ Rows_log_event::Rows_log_event(const uchar *buf, size_t event_len, case RW_V_EXTRAINFO_TAG: { /* Have an 'extra info' section, read it in */ - assert((end - pos) >= EXTRA_ROW_INFO_HDR_BYTES); + if (unlikely((end - pos) <= EXTRA_ROW_INFO_LEN_OFFSET)) + { + m_cols.bitmap= 0; + DBUG_VOID_RETURN; + } uint8 infoLen= pos[EXTRA_ROW_INFO_LEN_OFFSET]; - assert((end - pos) >= infoLen); + if (unlikely(infoLen < EXTRA_ROW_INFO_HDR_BYTES || (end-pos) < infoLen)) + { + m_cols.bitmap= 0; + DBUG_VOID_RETURN; + } /* Just store/use the first tag of this type, skip others */ if (likely(!m_extra_row_data)) { diff --git a/sql/log_event.h b/sql/log_event.h index 43a3b7ff76021..5599dc425915d 100644 --- a/sql/log_event.h +++ b/sql/log_event.h @@ -198,7 +198,7 @@ class String; #define STOP_HEADER_LEN 0 #define LOAD_HEADER_LEN (4 + 4 + 4 + 1 +1 + 4) #define SLAVE_HEADER_LEN 0 -#define START_V3_HEADER_LEN (2 + ST_SERVER_VER_LEN + 4) +#define START_V3_HEADER_LEN ST_COMMON_HEADER_LEN_OFFSET #define ROTATE_HEADER_LEN 8 // this is FROZEN (the Rotate post-header is frozen) #define INTVAR_HEADER_LEN 0 #define CREATE_FILE_HEADER_LEN 4 @@ -280,6 +280,7 @@ class String; #define ST_SERVER_VER_OFFSET 2 #define ST_CREATED_OFFSET (ST_SERVER_VER_OFFSET + ST_SERVER_VER_LEN) #define ST_COMMON_HEADER_LEN_OFFSET (ST_CREATED_OFFSET + 4) +#define ST_POST_HEADER_LEN_OFFSET (ST_COMMON_HEADER_LEN_OFFSET + 1) /* slave event post-header (this event is never written) */ @@ -6204,7 +6205,8 @@ bool slave_execute_deferred_events(THD *thd); bool event_that_should_be_ignored(const uchar *buf); bool event_checksum_test(uchar *buf, size_t event_len, enum_binlog_checksum_alg alg); -enum_binlog_checksum_alg get_checksum_alg(const uchar *buf, size_t len); +bool get_checksum_alg(const uchar *buf, size_t len, + enum_binlog_checksum_alg *alg); extern TYPELIB binlog_checksum_typelib; #ifdef WITH_WSREP enum Log_event_type wsrep_peak_event(rpl_group_info *rgi, ulonglong* event_size); diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index b37e4d5f1e406..ca089705cdc22 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -2459,7 +2459,12 @@ Format_description_log_event::to_packet(String *packet) { uchar *p; uint32 needed_length= - packet->length() + START_V3_HEADER_LEN + 1 + number_of_event_types + 1; + packet->length() + DBUG_IF("truncate_fde_common_header_len") ? + ST_COMMON_HEADER_LEN_OFFSET : ST_POST_HEADER_LEN_OFFSET; + if (!DBUG_IF("truncate_fde_post_header_len")) + needed_length += number_of_event_types; + if (!DBUG_IF("truncate_fde_used_checksum_alg")) + needed_length += BINLOG_CHECKSUM_ALG_DESC_LEN; if (packet->reserve(needed_length)) return true; p= (uchar *)packet->ptr() + packet->length();; @@ -2472,9 +2477,13 @@ Format_description_log_event::to_packet(String *packet) created= get_time(); int4store(p, created); p+= 4; - *p++= common_header_len; - memcpy(p, post_header_len, number_of_event_types); - p+= number_of_event_types; + if (!DBUG_IF("truncate_fde_common_header_len")) + *p++= common_header_len; + if (!DBUG_IF("truncate_fde_post_header_len")) + { + memcpy(p, post_header_len, number_of_event_types); + p+= number_of_event_types; + } /* if checksum is requested @@ -2500,7 +2509,8 @@ Format_description_log_event::to_packet(String *packet) (A), (V) presence in FD of the checksum-aware server makes the event 1 + 4 bytes bigger comparing to the former FD. */ - *p++= checksum_byte; + if (!DBUG_IF("truncate_fde_used_checksum_alg")) + *p++= checksum_byte; return false; } @@ -2519,7 +2529,7 @@ bool Format_description_log_event::write(Log_event_writer *writer) if (to_packet(&packet)) return true; size_t rec_size= packet.length(); - DBUG_ASSERT(needed == rec_size); + DBUG_ASSERT(needed >= rec_size); uint orig_checksum_len= writer->checksum_len; writer->checksum_len= BINLOG_CHECKSUM_LEN; diff --git a/sql/slave.cc b/sql/slave.cc index 51cef8087f958..1a4a302afef1f 100644 --- a/sql/slave.cc +++ b/sql/slave.cc @@ -5890,7 +5890,12 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) */ if (buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT) { - checksum_alg= get_checksum_alg(buf, event_len); + if (unlikely(get_checksum_alg(buf, event_len, &checksum_alg))) + { + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + unlock_data_lock= FALSE; + goto err; + } } else if (buf[EVENT_TYPE_OFFSET] == START_EVENT_V3) { @@ -5972,6 +5977,17 @@ static int queue_event(Master_info* mi, const uchar *buf, ulong event_len) goto err; case ROTATE_EVENT: { + /* + This is normally done in Log_event::read_log_event(), + but we bypass it here because it's expensive and costs dynamic memory. + */ + if (unlikely(ROTATE_EVENT > + mi->rli.relay_log.description_event_for_queue->number_of_event_types)) + { + // The current FDE does not support `ROTATE_EVENT`. + error= ER_SLAVE_RELAY_LOG_WRITE_FAILURE; + goto err; + } Rotate_log_event rev(buf, checksum_alg != BINLOG_CHECKSUM_ALG_OFF ? event_len - BINLOG_CHECKSUM_LEN : event_len, mi->rli.relay_log.description_event_for_queue); diff --git a/sql/sql_repl.cc b/sql/sql_repl.cc index b6fc23b51b6b5..8ba89a803911e 100644 --- a/sql/sql_repl.cc +++ b/sql/sql_repl.cc @@ -1956,10 +1956,10 @@ gtid_state_from_pos(const char *name, uint32 offset, goto end; } - current_checksum_alg= get_checksum_alg((uchar*) packet.ptr(), - packet.length()); found_format_description_event= true; - if (unlikely(!(tmp= new Format_description_log_event((uchar*) packet.ptr(), + if (unlikely(get_checksum_alg((uchar*) packet.ptr(), packet.length(), + ¤t_checksum_alg) || + !(tmp= new Format_description_log_event((uchar*)packet.ptr(), packet.length(), fdev)))) { @@ -2773,6 +2773,8 @@ static int init_binlog_sender(binlog_send_info *info, static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, LOG_INFO *linfo, my_off_t start_pos) { + static constexpr const char* CORRUPT_FDE= + "Corrupt Format_description event found or out-of-memory"; int error; ulong ev_offset; THD *thd= info->thd; @@ -2841,9 +2843,14 @@ static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, DBUG_RETURN(1); } - info->current_checksum_alg= get_checksum_alg((uchar*) packet->ptr() + - ev_offset, - packet->length() - ev_offset); + if (unlikely(get_checksum_alg((uchar*) packet->ptr() + ev_offset, + packet->length() - ev_offset, + &(info->current_checksum_alg)))) + { + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; + info->errmsg= CORRUPT_FDE; + DBUG_RETURN(1); + } DBUG_ASSERT(info->current_checksum_alg == BINLOG_CHECKSUM_ALG_OFF || info->current_checksum_alg == BINLOG_CHECKSUM_ALG_UNDEF || @@ -2871,8 +2878,7 @@ static int send_format_descriptor_event(binlog_send_info *info, IO_CACHE *log, ev_len, info->fdev))) { info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; - info->errmsg= "Corrupt Format_description event found " - "or out-of-memory"; + info->errmsg= CORRUPT_FDE; DBUG_RETURN(1); } delete info->fdev;