summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoao Gramacho <joao.gramacho@oracle.com>2018-02-02 11:45:56 +0000
committerJoao Gramacho <joao.gramacho@oracle.com>2018-02-02 11:45:56 +0000
commit3fb2f8db179c2ea9a15fcc2f142c5b98c5aab17a (patch)
tree730795e568eeeda2480567b628141657f154a083
parent2af9e8af6efba951e33e148d0b1a34beb25be831 (diff)
downloadmariadb-git-3fb2f8db179c2ea9a15fcc2f142c5b98c5aab17a.tar.gz
BUG#24365972 BINLOG DECODING ISN'T RESILIENT TO CORRUPT BINLOG FILES
Problem ======= When facing decoding of corrupt binary log files, server may misbehave without detecting the events corruption. This patch makes MySQL server more resilient to binary log decoding. Fixes for events de-serialization and apply =========================================== @sql/log_event.cc Query_log_event::Query_log_event: added a check to ensure query length is respecting event buffer limits. Query_log_event::do_apply_event: extended a debug print, added a check to character set to determine if it is "parseable" or not, verified if database name is valid for system collation. Start_log_event_v3::do_apply_event: report an error on applying a non-supported binary log version. Load_log_event::copy_log_event: added a check to table_name length. User_var_log_event::User_var_log_event: added checks to avoid reading out of buffer limits. User_var_log_event::do_apply_event: reported an sanity check error properly and added individual sanity checks for variable types that expect fixed (or minimum) amount of bytes to be read. Rows_log_event::Rows_log_event: added checks to avoid reading out of buffer limits. @sql/log_event_old.cc Old_rows_log_event::Old_rows_log_event: added a sanity check to avoid reading out of buffer limits. @sql/sql_priv.h Added a sanity check to available_buffer() function.
-rw-r--r--sql/log_event.cc126
-rw-r--r--sql/log_event_old.cc11
-rw-r--r--sql/sql_priv.h7
3 files changed, 136 insertions, 8 deletions
diff --git a/sql/log_event.cc b/sql/log_event.cc
index 5dbeb1eb4b9..ac1e105be8c 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -1,5 +1,5 @@
/*
- Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -3024,6 +3024,25 @@ Query_log_event::Query_log_event(const char* buf, uint event_len,
db= (char *)start;
query= (char *)(start + db_len + 1);
q_len= data_len - db_len -1;
+
+ if (data_len && (data_len < db_len ||
+ data_len < q_len ||
+ data_len != (db_len + q_len + 1)))
+ {
+ q_len= 0;
+ query= NULL;
+ DBUG_VOID_RETURN;
+ }
+
+ unsigned int max_length;
+ max_length= (event_len - ((const char*)(end + db_len + 1) -
+ (buf - common_header_len)));
+ if (q_len != max_length)
+ {
+ q_len= 0;
+ query= NULL;
+ DBUG_VOID_RETURN;
+ }
/**
Append the db length at the end of the buffer. This will be used by
Query_cache::send_result_to_client() in case the query cache is On.
@@ -3278,6 +3297,26 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli,
you.
*/
thd->catalog= catalog_len ? (char *) catalog : (char *)"";
+
+ size_t valid_len;
+ bool len_error;
+ bool is_invalid_db_name= validate_string(system_charset_info, db, db_len,
+ &valid_len, &len_error);
+
+ DBUG_PRINT("debug",("is_invalid_db_name= %s, valid_len=%zu, len_error=%s",
+ is_invalid_db_name ? "true" : "false",
+ valid_len,
+ len_error ? "true" : "false"));
+
+ if (is_invalid_db_name || len_error)
+ {
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
+ ER_THD(thd, ER_SLAVE_FATAL_ERROR),
+ "Invalid database name in Query event.");
+ thd->is_slave_error= true;
+ goto end;
+ }
+
new_db.length= db_len;
new_db.str= (char *) rpl_filter->get_rewrite_db(db, &new_db.length);
thd->set_db(new_db.str, new_db.length); /* allocates a copy of 'db' */
@@ -3454,7 +3493,23 @@ int Query_log_event::do_apply_event(Relay_log_info const *rli,
}
else
thd->variables.collation_database= thd->db_charset;
-
+
+ {
+ const CHARSET_INFO *cs= thd->charset();
+ /*
+ We cannot ask for parsing a statement using a character set
+ without state_maps (parser internal data).
+ */
+ if (!cs->state_map)
+ {
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
+ ER_THD(thd, ER_SLAVE_FATAL_ERROR),
+ "character_set cannot be parsed");
+ thd->is_slave_error= true;
+ goto end;
+ }
+ }
+
thd->table_map_for_update= (table_map)table_map_for_update;
thd->set_invoker(&user, &host);
/*
@@ -3898,7 +3953,13 @@ int Start_log_event_v3::do_apply_event(Relay_log_info const *rli)
*/
break;
default:
- /* this case is impossible */
+ /*
+ This case is not expected. It can be either an event corruption or an
+ unsupported binary log version.
+ */
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
+ ER_THD(thd, ER_SLAVE_FATAL_ERROR),
+ "Binlog version not supported");
DBUG_RETURN(1);
}
DBUG_RETURN(error);
@@ -4724,6 +4785,9 @@ int Load_log_event::copy_log_event(const char *buf, ulong event_len,
fields = (char*)field_lens + num_fields;
table_name = fields + field_block_len;
+ if (strlen(table_name) > NAME_LEN)
+ goto err;
+
db = table_name + table_name_len + 1;
DBUG_EXECUTE_IF ("simulate_invalid_address",
db_len = data_len;);
@@ -5889,6 +5953,13 @@ User_var_log_event(const char* buf, uint event_len,
buf+= description_event->common_header_len +
description_event->post_header_len[USER_VAR_EVENT-1];
name_len= uint4korr(buf);
+ /* Avoid reading out of buffer */
+ if ((buf - buf_start) + UV_NAME_LEN_SIZE + name_len > event_len)
+ {
+ error= true;
+ goto err;
+ }
+
name= (char *) buf + UV_NAME_LEN_SIZE;
/*
@@ -5948,8 +6019,11 @@ User_var_log_event(const char* buf, uint event_len,
we keep the flags set to UNDEF_F.
*/
uint bytes_read= ((val + val_len) - start);
- DBUG_ASSERT(bytes_read==data_written ||
- bytes_read==(data_written-1));
+ if (bytes_read > event_len)
+ {
+ error= true;
+ goto err;
+ }
if ((data_written - bytes_read) > 0)
{
flags= (uint) *(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
@@ -6165,7 +6239,12 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli)
}
if (!(charset= get_charset(charset_number, MYF(MY_WME))))
+ {
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
+ ER_THD(thd, ER_SLAVE_FATAL_ERROR),
+ "Invalid character set for User var event");
return 1;
+ }
LEX_STRING user_var_name;
user_var_name.str= name;
user_var_name.length= name_len;
@@ -6186,12 +6265,26 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli)
{
switch (type) {
case REAL_RESULT:
+ if (val_len != 8)
+ {
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
+ ER_THD(thd, ER_SLAVE_FATAL_ERROR),
+ "Invalid variable length at User var event");
+ return 1;
+ }
float8get(real_val, val);
it= new Item_float(real_val, 0);
val= (char*) &real_val; // Pointer to value in native format
val_len= 8;
break;
case INT_RESULT:
+ if (val_len != 8)
+ {
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
+ ER_THD(thd, ER_SLAVE_FATAL_ERROR),
+ "Invalid variable length at User var event");
+ return 1;
+ }
int_val= (longlong) uint8korr(val);
it= new Item_int(int_val);
val= (char*) &int_val; // Pointer to value in native format
@@ -6199,6 +6292,13 @@ int User_var_log_event::do_apply_event(Relay_log_info const *rli)
break;
case DECIMAL_RESULT:
{
+ if (val_len < 3)
+ {
+ rli->report(ERROR_LEVEL, ER_SLAVE_FATAL_ERROR,
+ ER_THD(thd, ER_SLAVE_FATAL_ERROR),
+ "Invalid variable length at User var event");
+ return 1;
+ }
Item_decimal *dec= new Item_decimal((uchar*) val+2, val[0], val[1]);
it= dec;
val= (char *)dec->val_decimal(NULL);
@@ -7646,6 +7746,15 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len,
DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
m_width = net_field_length(&ptr_after_width);
DBUG_PRINT("debug", ("m_width=%lu", m_width));
+ /* Avoid reading out of buffer */
+ if (static_cast<unsigned int>((ptr_after_width +
+ (m_width + 7) / 8) -
+ (uchar*)buf) > event_len)
+ {
+ m_cols.bitmap= NULL;
+ DBUG_VOID_RETURN;
+ }
+
/* if bitmap_init fails, catched in is_valid() */
if (likely(!bitmap_init(&m_cols,
m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL,
@@ -7694,7 +7803,12 @@ Rows_log_event::Rows_log_event(const char *buf, uint event_len,
const uchar* const ptr_rows_data= (const uchar*) ptr_after_width;
- size_t const data_size= event_len - (ptr_rows_data - (const uchar *) buf);
+ size_t const read_size= ptr_rows_data - (const unsigned char *) buf;
+ if (read_size > event_len)
+ {
+ DBUG_VOID_RETURN;
+ }
+ size_t const data_size= event_len - read_size;
DBUG_PRINT("info",("m_table_id: %lu m_flags: %d m_width: %lu data_size: %lu",
m_table_id, m_flags, m_width, (ulong) data_size));
diff --git a/sql/log_event_old.cc b/sql/log_event_old.cc
index eb9678ddaa5..6be4e086925 100644
--- a/sql/log_event_old.cc
+++ b/sql/log_event_old.cc
@@ -1,4 +1,4 @@
-/* Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
+/* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -1357,6 +1357,15 @@ Old_rows_log_event::Old_rows_log_event(const char *buf, uint event_len,
DBUG_PRINT("debug", ("Reading from %p", ptr_after_width));
m_width = net_field_length(&ptr_after_width);
DBUG_PRINT("debug", ("m_width=%lu", m_width));
+ /* Avoid reading out of buffer */
+ if (static_cast<unsigned int>(m_width +
+ (ptr_after_width -
+ (const uchar *)buf)) > event_len)
+ {
+ m_cols.bitmap= NULL;
+ DBUG_VOID_RETURN;
+ }
+
/* if bitmap_init fails, catched in is_valid() */
if (likely(!bitmap_init(&m_cols,
m_width <= sizeof(m_bitbuf)*8 ? m_bitbuf : NULL,
diff --git a/sql/sql_priv.h b/sql/sql_priv.h
index 523220b3c03..b12d22e3fc7 100644
--- a/sql/sql_priv.h
+++ b/sql/sql_priv.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
+/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -191,6 +191,11 @@ template <class T> T available_buffer(const char* buf_start,
const char* buf_current,
T buf_len)
{
+ /* Sanity check */
+ if (buf_current < buf_start ||
+ buf_len < static_cast<T>(buf_current - buf_start))
+ return static_cast<T>(0);
+
return buf_len - (buf_current - buf_start);
}