Commit 873cc1e7 authored by Sujatha's avatar Sujatha

MDEV-21839: Handle crazy offset to SHOW BINLOG EVENTS

Problem:
=======
SHOW BINLOG EVENTS FROM <"random"-pos> caused a variety of failures as
reported in MDEV-18046. They are fixed but that approach is not future-proof
as well as is not optimal to create extra check for being constructed event
parameters.

Analysis:
=========
"show binlog events from <pos>" code considers the user given position as a
valid event start position. The code starts reading data from this event start
position onwards and tries to map it to a set of known events. Each event has
a specific event structure and asserts have been added to ensure that, read
event data, satisfies the event specific requirements. When a random position
is supplied to "show binlog events command" the event structure specific
checks will fail and they result in assert.

For example: https://jira.mariadb.org/browse/MDEV-18046
In the bug description user executes CREATE TABLE/INSERT and ALTER SQL
commands.

When a crazy offset like "SHOW BINLOG EVENTS FROM 365" is provided code
assumes offset 365 as valid event begin and proceeds to EVENT_LEN_OFFSET reads
some random length and comes up with a crazy event which didn't exits in the
binary log. In this quoted example scenario, event read at offset 365 is
considered as "Update_rows_log_event", which is not present in binary log.
Since this is a random event its validation fails and code results in
assert/segmentation fault, as shown below.

mysqld: /data/src/10.4/sql/log_event.cc:10863: Rows_log_event::Rows_log_event(
    const char*, uint, const Format_description_log_event*):
    Assertion `var_header_len >= 2' failed.
    181220 15:27:02 [ERROR] mysqld got signal 6 ;
#7  0x00007fa0d96abee2 in __assert_fail () from /lib/x86_64-linux-gnu/libc.so.6
#8  0x000055e744ef82de in Rows_log_event::Rows_log_event (this=0x7fa05800d390,
    buf=0x7fa05800d080 "", event_len=254, description_event=0x7fa058006d60) at
/data/src/10.4/sql/log_event.cc:10863
#9  0x000055e744f00cf8 in Update_rows_log_event::Update_rows_log_event

Since we are reading random data repeating the same command SHOW BINLOG EVENTS
FROM 365 produces different types of crashes with different events. MDEV-18046
reported 10 such crashes.

In order to avoid such scenarios user provided starting offset needs to be
validated for its correctness. Best way of doing this is to make use of
checksums if they are available. MDEV-18046 fix introduced the checksum based
validation.

The issue still remains in cases where binlog checksums are disabled. Please
find the following bug reports.

MDEV-22473: binlog.binlog_show_binlog_event_random_pos failed in buildbot,
            server crashed in read_log_event
MDEV-22455: Server crashes in Table_map_log_event,
            binlog.binlog_invalid_read_in_rotate failed in buildbot

Fix:
====
When binlog checksum is disabled, perform scan(via reading event by event), to
validate the requested FROM <pos> offset. Starting from offset 4 read the
event_length of next_event in the binary log. Using the next_event length
advance current offset to point to next event. Repeat this process till the
current offset is less than or equal to crazy offset. If current offset is
higher than crazy offset provide appropriate invalid input offset error.
parent 5768f57d
...@@ -56,7 +56,7 @@ master-bin.000001 # Query # # use `test`; DROP TABLE `t4` /* generated by server ...@@ -56,7 +56,7 @@ master-bin.000001 # Query # # use `test`; DROP TABLE `t4` /* generated by server
End of 5.0 tests End of 5.0 tests
call mtr.add_suppression("Error in Log_event::read_log_event\\\(\\\): 'Found invalid"); call mtr.add_suppression("Error in Log_event::read_log_event\\\(\\\): 'Found invalid");
SHOW BINLOG EVENTS FROM 504; SHOW BINLOG EVENTS FROM 504;
ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Wrong offset or I/O error ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Invalid input pos specified please provide valid one.
Bug#44352 UPPER/LOWER function doesn't work correctly on cp932 and sjis environment. Bug#44352 UPPER/LOWER function doesn't work correctly on cp932 and sjis environment.
CREATE TABLE t1 (a varchar(16)) character set cp932; CREATE TABLE t1 (a varchar(16)) character set cp932;
INSERT INTO t1 VALUES (0x8372835E),(0x8352835E); INSERT INTO t1 VALUES (0x8372835E),(0x8352835E);
......
...@@ -9,4 +9,7 @@ INSERT INTO t1 VALUES (repeat('a', 255), repeat('a', 255),repeat('a', 255),repea ...@@ -9,4 +9,7 @@ INSERT INTO t1 VALUES (repeat('a', 255), repeat('a', 255),repeat('a', 255),repea
INSERT INTO t1 VALUES (repeat('a', 255), repeat('a', 255),repeat('a', 255),repeat('a', 255),repeat('a', 255)); INSERT INTO t1 VALUES (repeat('a', 255), repeat('a', 255),repeat('a', 255),repeat('a', 255),repeat('a', 255));
UPDATE t1 SET c1=repeat('b',255); UPDATE t1 SET c1=repeat('b',255);
INSERT INTO t1 VALUES (repeat('a', 255), repeat('a', 255),repeat('a', 255),repeat('a', 255),repeat('a', 255)); INSERT INTO t1 VALUES (repeat('a', 255), repeat('a', 255),repeat('a', 255),repeat('a', 255),repeat('a', 255));
SHOW BINLOG EVENTS FROM POS;
ERROR HY000: Error when executing command SHOW BINLOG EVENTS: Invalid pos specified. Requested from pos:POS is greater than actual file size:MAX_POS
DROP TABLE t1; DROP TABLE t1;
...@@ -34,4 +34,9 @@ while ($pos <= $max_pos) ...@@ -34,4 +34,9 @@ while ($pos <= $max_pos)
--enable_query_log --enable_query_log
} }
# Testing a case where input position is greater than actual binlog file size.
--replace_result $pos POS $max_pos MAX_POS
--error 1220
eval SHOW BINLOG EVENTS FROM $pos;
DROP TABLE t1; DROP TABLE t1;
...@@ -3842,6 +3842,7 @@ bool mysql_show_binlog_events(THD* thd) ...@@ -3842,6 +3842,7 @@ bool mysql_show_binlog_events(THD* thd)
{ {
Protocol *protocol= thd->protocol; Protocol *protocol= thd->protocol;
List<Item> field_list; List<Item> field_list;
char errmsg_buf[MYSYS_ERRMSG_SIZE];
const char *errmsg = 0; const char *errmsg = 0;
bool ret = TRUE; bool ret = TRUE;
/* /*
...@@ -3856,6 +3857,9 @@ bool mysql_show_binlog_events(THD* thd) ...@@ -3856,6 +3857,9 @@ bool mysql_show_binlog_events(THD* thd)
Master_info *mi= 0; Master_info *mi= 0;
LOG_INFO linfo; LOG_INFO linfo;
LEX_MASTER_INFO *lex_mi= &thd->lex->mi; LEX_MASTER_INFO *lex_mi= &thd->lex->mi;
enum enum_binlog_checksum_alg checksum_alg;
my_off_t binlog_size;
MY_STAT s;
DBUG_ENTER("mysql_show_binlog_events"); DBUG_ENTER("mysql_show_binlog_events");
...@@ -3904,10 +3908,6 @@ bool mysql_show_binlog_events(THD* thd) ...@@ -3904,10 +3908,6 @@ bool mysql_show_binlog_events(THD* thd)
mi= 0; mi= 0;
} }
/* Validate user given position using checksum */
if (lex_mi->pos == pos && !opt_master_verify_checksum)
verify_checksum_once= true;
unit->set_limit(thd->lex->current_select); unit->set_limit(thd->lex->current_select);
limit_start= unit->offset_limit_cnt; limit_start= unit->offset_limit_cnt;
limit_end= unit->select_limit_cnt; limit_end= unit->select_limit_cnt;
...@@ -3934,6 +3934,17 @@ bool mysql_show_binlog_events(THD* thd) ...@@ -3934,6 +3934,17 @@ bool mysql_show_binlog_events(THD* thd)
if ((file=open_binlog(&log, linfo.log_file_name, &errmsg)) < 0) if ((file=open_binlog(&log, linfo.log_file_name, &errmsg)) < 0)
goto err; goto err;
my_stat(linfo.log_file_name, &s, MYF(0));
binlog_size= s.st_size;
if (lex_mi->pos > binlog_size)
{
sprintf(errmsg_buf, "Invalid pos specified. Requested from pos:%llu is "
"greater than actual file size:%lu\n", lex_mi->pos,
(ulong)s.st_size);
errmsg= errmsg_buf;
goto err;
}
/* /*
to account binlog event header size to account binlog event header size
*/ */
...@@ -3985,7 +3996,43 @@ bool mysql_show_binlog_events(THD* thd) ...@@ -3985,7 +3996,43 @@ bool mysql_show_binlog_events(THD* thd)
} }
} }
my_b_seek(&log, pos); if (lex_mi->pos > BIN_LOG_HEADER_SIZE)
{
checksum_alg= description_event->checksum_alg;
/* Validate user given position using checksum */
if (checksum_alg != BINLOG_CHECKSUM_ALG_OFF &&
checksum_alg != BINLOG_CHECKSUM_ALG_UNDEF)
{
if (!opt_master_verify_checksum)
verify_checksum_once= true;
my_b_seek(&log, pos);
}
else
{
my_off_t cur_pos= my_b_tell(&log);
ulong next_event_len= 0;
uchar buff[IO_SIZE];
while (cur_pos < pos)
{
my_b_seek(&log, cur_pos + EVENT_LEN_OFFSET);
if (my_b_read(&log, (uchar *)buff, sizeof(next_event_len)))
{
mysql_mutex_unlock(log_lock);
errmsg = "Could not read event_length";
goto err;
}
next_event_len= uint4korr(buff);
cur_pos= cur_pos + next_event_len;
}
if (cur_pos > pos)
{
mysql_mutex_unlock(log_lock);
errmsg= "Invalid input pos specified please provide valid one.";
goto err;
}
my_b_seek(&log, cur_pos);
}
}
for (event_count = 0; for (event_count = 0;
(ev = Log_event::read_log_event(&log, (mysql_mutex_t*) 0, (ev = Log_event::read_log_event(&log, (mysql_mutex_t*) 0,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment