summaryrefslogtreecommitdiff
path: root/sql
diff options
context:
space:
mode:
authorNuno Carvalho <nuno.carvalho@oracle.com>2012-10-12 08:32:10 +0100
committerNuno Carvalho <nuno.carvalho@oracle.com>2012-10-12 08:32:10 +0100
commitf1d3b0f19011217c13927f44a82e8e17291fbba7 (patch)
treea14d05446ba13c143defb677b91cdb209503b0f8 /sql
parent1d16fc16dc50d21e2456d6367cea20f83404ffc6 (diff)
downloadmariadb-git-f1d3b0f19011217c13927f44a82e8e17291fbba7.tar.gz
BUG#14629727: USER_VAR_EVENT IS MISSING RANGE CHECKS
This bug had two problems: P1) Reads out of bounds; P2) Writes out of bounds. PROBLEM P1 ---------- User_var_log_event unmarshalling from binlog was not performing range checks when using name_len and val_len variables to walk on event buffer. Added range checks to User_var_log_event unmarshalling to prevent unmarshalling errors. PROBLEM P2 ---------- User_var_log_event value was allocated on thread stack, what caused stack frame errors when User_var_log_event value was bigger than thread stack size. Currently value is allocated on heap memory.
Diffstat (limited to 'sql')
-rw-r--r--sql/log_event.cc45
-rw-r--r--sql/log_event.h4
-rw-r--r--sql/mysql_priv.h44
3 files changed, 86 insertions, 7 deletions
diff --git a/sql/log_event.cc b/sql/log_event.cc
index c3ba969cf1f..0ad258f7073 100644
--- a/sql/log_event.cc
+++ b/sql/log_event.cc
@@ -1286,7 +1286,7 @@ Log_event* Log_event::read_log_event(const char* buf, uint event_len,
ev = new Rand_log_event(buf, description_event);
break;
case USER_VAR_EVENT:
- ev = new User_var_log_event(buf, description_event);
+ ev = new User_var_log_event(buf, event_len, description_event);
break;
case FORMAT_DESCRIPTION_EVENT:
ev = new Format_description_log_event(buf, event_len, description_event);
@@ -5685,18 +5685,34 @@ void User_var_log_event::pack_info(Protocol* protocol)
User_var_log_event::
-User_var_log_event(const char* buf,
+User_var_log_event(const char* buf, uint event_len,
const Format_description_log_event* description_event)
:Log_event(buf, description_event)
#ifndef MYSQL_CLIENT
, deferred(false)
#endif
{
+ bool error= false;
+ const char* buf_start= buf;
/* The Post-Header is empty. The Variable Data part begins immediately. */
buf+= description_event->common_header_len +
description_event->post_header_len[USER_VAR_EVENT-1];
name_len= uint4korr(buf);
name= (char *) buf + UV_NAME_LEN_SIZE;
+
+ /*
+ We don't know yet is_null value, so we must assume that name_len
+ may have the bigger value possible, is_null= True and there is no
+ payload for val.
+ */
+ if (0 == name_len ||
+ !valid_buffer_range<uint>(name_len, buf_start, name,
+ event_len - UV_VAL_IS_NULL))
+ {
+ error= true;
+ goto err;
+ }
+
buf+= UV_NAME_LEN_SIZE + name_len;
is_null= (bool) *buf;
if (is_null)
@@ -5708,13 +5724,31 @@ User_var_log_event(const char* buf,
}
else
{
+ if (!valid_buffer_range<uint>(UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE
+ + UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE,
+ buf_start, buf, event_len))
+ {
+ error= true;
+ goto err;
+ }
+
type= (Item_result) buf[UV_VAL_IS_NULL];
charset_number= uint4korr(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE);
val_len= uint4korr(buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
UV_CHARSET_NUMBER_SIZE);
val= (char *) (buf + UV_VAL_IS_NULL + UV_VAL_TYPE_SIZE +
UV_CHARSET_NUMBER_SIZE + UV_VAL_LEN_SIZE);
+
+ if (!valid_buffer_range<uint>(val_len, buf_start, val, event_len))
+ {
+ error= true;
+ goto err;
+ }
}
+
+err:
+ if (error)
+ name= 0;
}
@@ -5860,8 +5894,9 @@ void User_var_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
char *hex_str;
CHARSET_INFO *cs;
- if (!(hex_str= (char *)my_alloca(2*val_len+1+2))) // 2 hex digits / byte
- break; // no error, as we are 'void'
+ hex_str= (char *)my_malloc(2*val_len+1+2,MYF(MY_WME)); // 2 hex digits / byte
+ if (!hex_str)
+ return;
str_to_hex(hex_str, val, val_len);
/*
For proper behaviour when mysqlbinlog|mysql, we need to explicitely
@@ -5879,7 +5914,7 @@ void User_var_log_event::print(FILE* file, PRINT_EVENT_INFO* print_event_info)
my_b_printf(&cache, ":=_%s %s COLLATE `%s`%s\n",
cs->csname, hex_str, cs->name,
print_event_info->delimiter);
- my_afree(hex_str);
+ my_free(hex_str, MYF(MY_WME));
}
break;
case ROW_RESULT:
diff --git a/sql/log_event.h b/sql/log_event.h
index ba6b9b876aa..c36564fcde8 100644
--- a/sql/log_event.h
+++ b/sql/log_event.h
@@ -2496,7 +2496,7 @@ public:
void print(FILE* file, PRINT_EVENT_INFO* print_event_info);
#endif
- User_var_log_event(const char* buf,
+ User_var_log_event(const char* buf, uint event_len,
const Format_description_log_event *description_event);
~User_var_log_event() {}
Log_event_type get_type_code() { return USER_VAR_EVENT;}
@@ -2510,7 +2510,7 @@ public:
bool is_deferred() { return deferred; }
void set_deferred() { deferred= true; }
#endif
- bool is_valid() const { return 1; }
+ bool is_valid() const { return name != 0; }
private:
#if !defined(MYSQL_CLIENT) && defined(HAVE_REPLICATION)
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index 937617032dd..05a37228e17 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -501,6 +501,50 @@ protected:
*/
#define MAX_TIME_ZONE_NAME_LENGTH (NAME_LEN + 1)
+/*
+ Check how many bytes are available on buffer.
+
+ @param buf_start Pointer to buffer start.
+ @param buf_current Pointer to the current position on buffer.
+ @param buf_len Buffer length.
+
+ @return Number of bytes available on event buffer.
+*/
+template <class T> T available_buffer(const char* buf_start,
+ const char* buf_current,
+ T buf_len)
+{
+ return buf_len - (buf_current - buf_start);
+}
+/* Explicit instantion to unsigned int. */
+template unsigned int available_buffer<unsigned int>(const char*,
+ const char*,
+ unsigned int);
+
+/*
+ Check if jump value is within buffer limits.
+
+ @param jump Number of positions we want to advance.
+ @param buf_start Pointer to buffer start
+ @param buf_current Pointer to the current position on buffer.
+ @param buf_len Buffer length.
+
+ @return True If jump value is within buffer limits.
+ False Otherwise.
+*/
+template <class T> bool valid_buffer_range(T jump,
+ const char* buf_start,
+ const char* buf_current,
+ T buf_len)
+{
+ return (jump <= available_buffer(buf_start, buf_current, buf_len));
+}
+/* Explicit instantion to unsigned int. */
+template bool valid_buffer_range<unsigned int>(unsigned int,
+ const char*,
+ const char*,
+ unsigned int);
+
/* The rest of the file is included in the server only */
#ifndef MYSQL_CLIENT