From 727757d299c2476488543c5758f50ae6e15d4211 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 7 Jun 2007 13:50:22 +0400 Subject: [PATCH 1/6] Fix for BUG#27592: stack overrun when storing datetime value using prepared statements. sql/field.cc: Using MAX_DATETIME_WIDTH or MAX_DATETIME_COMPRESSED_WIDTH constants for the length of DATETIME fields. Using MAX_DATE_STRING_REP_LENGTH for allocating buffers for date/time/... string representation. sql/item_timefunc.cc: Using MAX_DATETIME_WIDTH or MAX_DATETIME_COMPRESSED_WIDTH constants for the length of DATETIME fields. Using MAX_DATE_STRING_REP_LENGTH for allocating buffers for date/time/... string representation. sql/unireg.h: Introduce a constant for length of datetime compressed format (YYYYMMDDHHMMSS). --- sql/field.cc | 24 ++++++++++++++---------- sql/item_timefunc.cc | 32 +++++++++++++++++++++----------- sql/unireg.h | 1 + 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/sql/field.cc b/sql/field.cc index 8186659ae89..d69324141ce 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -4285,7 +4285,7 @@ Field_timestamp::Field_timestamp(char *ptr_arg, uint32 len_arg, const char *field_name_arg, struct st_table *table_arg, CHARSET_INFO *cs) - :Field_str(ptr_arg, 19, null_ptr_arg, null_bit_arg, + :Field_str(ptr_arg, MAX_DATETIME_WIDTH, null_ptr_arg, null_bit_arg, unireg_check_arg, field_name_arg, table_arg, cs) { /* For 4.0 MYD and 4.0 InnoDB compatibility */ @@ -4303,7 +4303,8 @@ Field_timestamp::Field_timestamp(char *ptr_arg, uint32 len_arg, Field_timestamp::Field_timestamp(bool maybe_null_arg, const char *field_name_arg, struct st_table *table_arg, CHARSET_INFO *cs) - :Field_str((char*) 0, 19, maybe_null_arg ? (uchar*) "": 0, 0, + :Field_str((char*) 0, MAX_DATETIME_WIDTH, + maybe_null_arg ? (uchar*) "": 0, 0, NONE, field_name_arg, table_arg, cs) { /* For 4.0 MYD and 4.0 InnoDB compatibility */ @@ -4834,7 +4835,7 @@ String *Field_time::val_str(String *val_buffer, String *val_ptr __attribute__((unused))) { MYSQL_TIME ltime; - val_buffer->alloc(19); + val_buffer->alloc(MAX_DATE_STRING_REP_LENGTH); long tmp=(long) sint3korr(ptr); ltime.neg= 0; if (tmp < 0) @@ -5370,7 +5371,7 @@ int Field_newdate::store_time(MYSQL_TIME *ltime, timestamp_type time_type) (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_INVALID_DATES))), &error)) { - char buff[12]; + char buff[MAX_DATE_STRING_REP_LENGTH]; String str(buff, sizeof(buff), &my_charset_latin1); make_date((DATE_TIME_FORMAT *) 0, ltime, &str); set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, @@ -5595,7 +5596,7 @@ int Field_datetime::store_time(MYSQL_TIME *ltime,timestamp_type time_type) (MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_INVALID_DATES))), &error)) { - char buff[19]; + char buff[MAX_DATE_STRING_REP_LENGTH]; String str(buff, sizeof(buff), &my_charset_latin1); make_datetime((DATE_TIME_FORMAT *) 0, ltime, &str); set_datetime_warning(MYSQL_ERROR::WARN_LEVEL_WARN, WARN_DATA_TRUNCATED, @@ -5669,7 +5670,7 @@ String *Field_datetime::val_str(String *val_buffer, part1=(long) (tmp/LL(1000000)); part2=(long) (tmp - (ulonglong) part1*LL(1000000)); - pos=(char*) val_buffer->ptr()+19; + pos= (char*) val_buffer->ptr() + MAX_DATETIME_WIDTH; *pos--=0; *pos--= (char) ('0'+(char) (part2%10)); part2/=10; *pos--= (char) ('0'+(char) (part2%10)); part3= (int) (part2 / 10); @@ -8565,15 +8566,18 @@ bool create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, break; case FIELD_TYPE_TIMESTAMP: if (!fld_length) - length= 14; /* Full date YYYYMMDDHHMMSS */ - else if (length != 19) + { + /* Compressed date YYYYMMDDHHMMSS */ + length= MAX_DATETIME_COMPRESSED_WIDTH; + } + else if (length != MAX_DATETIME_WIDTH) { /* We support only even TIMESTAMP lengths less or equal than 14 and 19 as length of 4.1 compatible representation. */ length= ((length+1)/2)*2; /* purecov: inspected */ - length= min(length,14); /* purecov: inspected */ + length= min(length, MAX_DATETIME_COMPRESSED_WIDTH); /* purecov: inspected */ } flags|= ZEROFILL_FLAG | UNSIGNED_FLAG; if (fld_default_value) @@ -8626,7 +8630,7 @@ bool create_field::init(THD *thd, char *fld_name, enum_field_types fld_type, length= 10; break; case FIELD_TYPE_DATETIME: - length= 19; + length= MAX_DATETIME_WIDTH; break; case FIELD_TYPE_SET: { diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index f5895369a55..5ac0a90c28a 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -51,7 +51,7 @@ static bool make_datetime(date_time_format_types format, MYSQL_TIME *ltime, { char *buff; CHARSET_INFO *cs= &my_charset_bin; - uint length= 30; + uint length= MAX_DATE_STRING_REP_LENGTH; if (str->alloc(length)) return 1; @@ -1400,7 +1400,7 @@ String *Item_date::val_str(String *str) MYSQL_TIME ltime; if (get_date(<ime, TIME_FUZZY_DATE)) return (String *) 0; - if (str->alloc(11)) + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) { null_value= 1; return (String *) 0; @@ -1449,7 +1449,7 @@ void Item_func_curdate::fix_length_and_dec() String *Item_func_curdate::val_str(String *str) { DBUG_ASSERT(fixed == 1); - if (str->alloc(11)) + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) { null_value= 1; return (String *) 0; @@ -1678,7 +1678,8 @@ String *Item_func_sec_to_time::val_str(String *str) MYSQL_TIME ltime; longlong arg_val= args[0]->val_int(); - if ((null_value=args[0]->null_value) || str->alloc(19)) + if ((null_value=args[0]->null_value) || + str->alloc(MAX_DATE_STRING_REP_LENGTH)) { null_value= 1; return (String*) 0; @@ -1863,6 +1864,10 @@ String *Item_func_date_format::val_str(String *str) size=max_length; else size=format_length(format); + + if (size < MAX_DATE_STRING_REP_LENGTH) + size= MAX_DATE_STRING_REP_LENGTH; + if (format == str) str= &value; // Save result here if (str->alloc(size)) @@ -1906,13 +1911,14 @@ String *Item_func_from_unixtime::val_str(String *str) if (get_date(&time_tmp, 0)) return 0; - if (str->alloc(20*MY_CHARSET_BIN_MB_MAXLEN)) + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) { null_value= 1; return 0; } make_datetime((DATE_TIME_FORMAT *) 0, &time_tmp, str); + return str; } @@ -1974,14 +1980,15 @@ String *Item_func_convert_tz::val_str(String *str) if (get_date(&time_tmp, 0)) return 0; - - if (str->alloc(20*MY_CHARSET_BIN_MB_MAXLEN)) + + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) { null_value= 1; return 0; } - + make_datetime((DATE_TIME_FORMAT *) 0, &time_tmp, str); + return str; } @@ -2582,6 +2589,7 @@ String *Item_datetime_typecast::val_str(String *str) { DBUG_ASSERT(fixed == 1); MYSQL_TIME ltime; + if (!get_arg0_date(<ime, TIME_FUZZY_DATE) && !make_datetime(ltime.second_part ? DATE_TIME_MICROSECOND : DATE_TIME, <ime, str)) @@ -2660,7 +2668,8 @@ String *Item_date_typecast::val_str(String *str) DBUG_ASSERT(fixed == 1); MYSQL_TIME ltime; - if (!get_arg0_date(<ime, TIME_FUZZY_DATE) && !str->alloc(11)) + if (!get_arg0_date(<ime, TIME_FUZZY_DATE) && + !str->alloc(MAX_DATE_STRING_REP_LENGTH)) { make_date((DATE_TIME_FORMAT *) 0, <ime, str); return str; @@ -2713,7 +2722,7 @@ String *Item_func_makedate::val_str(String *str) { null_value=0; get_date_from_daynr(days,&l_time.year,&l_time.month,&l_time.day); - if (str->alloc(11)) + if (str->alloc(MAX_DATE_STRING_REP_LENGTH)) goto err; make_date((DATE_TIME_FORMAT *) 0, &l_time, str); return str; @@ -2849,6 +2858,7 @@ String *Item_func_add_time::val_str(String *str) days= (long)(seconds/86400L); calc_time_from_sec(&l_time3, (long)(seconds%86400L), microseconds); + if (!is_time) { get_date_from_daynr(days,&l_time3.year,&l_time3.month,&l_time3.day); @@ -2964,7 +2974,7 @@ String *Item_func_maketime::val_str(String *str) args[2]->null_value || minute < 0 || minute > 59 || second < 0 || second > 59 || - str->alloc(19)))) + str->alloc(MAX_DATE_STRING_REP_LENGTH)))) return 0; bzero((char *)<ime, sizeof(ltime)); diff --git a/sql/unireg.h b/sql/unireg.h index 886b3d99212..0f7c1709c6c 100644 --- a/sql/unireg.h +++ b/sql/unireg.h @@ -71,6 +71,7 @@ #define MAX_TIME_WIDTH 23 /* -DDDDDD HH:MM:SS.###### */ #define MAX_DATETIME_FULL_WIDTH 29 /* YYYY-MM-DD HH:MM:SS.###### AM */ #define MAX_DATETIME_WIDTH 19 /* YYYY-MM-DD HH:MM:SS */ +#define MAX_DATETIME_COMPRESSED_WIDTH 14 /* YYYYMMDDHHMMSS */ #define MAX_TABLES (sizeof(table_map)*8-3) /* Max tables in join */ #define PARAM_TABLE_BIT (((table_map) 1) << (sizeof(table_map)*8-3)) From 2412446cd489675ffab7bcd3cc54a610fb0e74e1 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 7 Jun 2007 19:35:29 +0400 Subject: [PATCH 2/6] Add test case for BUG#27592: stack overrun when storing datetime value using prepared statements. tests/mysql_client_test.c: Test case. --- tests/mysql_client_test.c | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index e56dd693287..d3aa31a9400 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -15623,6 +15623,59 @@ static void test_bug27876() } +/* + Bug#27592 (stack overrun when storing datetime value using prepared statements) +*/ + +static void test_bug27592() +{ + const int NUM_ITERATIONS= 40; + int i; + int rc; + MYSQL_STMT *stmt= NULL; + MYSQL_BIND bind[1]; + MYSQL_TIME time_val; + + DBUG_ENTER("test_bug27592"); + myheader("test_bug27592"); + + mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + mysql_query(mysql, "CREATE TABLE t1(c2 DATETIME)"); + + stmt= mysql_simple_prepare(mysql, "INSERT INTO t1 VALUES (?)"); + DIE_UNLESS(stmt); + + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type= MYSQL_TYPE_DATETIME; + bind[0].buffer= (char *) &time_val; + bind[0].length= NULL; + + for (i= 0; i < NUM_ITERATIONS; i++) + { + time_val.year=1000+lrand48()%1000; + time_val.month=1+lrand48()%12; + time_val.day=1+lrand48()%31; + time_val.hour=lrand48()%23; + time_val.minute=lrand48()%59; + time_val.second=lrand48()%59; + + time_val.second_part=0; + time_val.neg=0; + + rc= mysql_stmt_bind_param(stmt, bind); + check_execute(stmt, rc); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + } + + mysql_stmt_close(stmt); + + DBUG_VOID_RETURN; +} + + /* Read and parse arguments and MySQL options from my.cnf */ @@ -15904,6 +15957,7 @@ static struct my_tests_st my_tests[]= { { "test_bug21635", test_bug21635 }, { "test_bug24179", test_bug24179 }, { "test_bug27876", test_bug27876 }, + { "test_bug27592", test_bug27592 }, { 0, 0 } }; From 0e958122e197f4d02cf07040e4cb24af460ab15e Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 7 Jun 2007 20:46:35 +0400 Subject: [PATCH 3/6] Fix Windows failure. --- tests/mysql_client_test.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index d3aa31a9400..bc429f371c8 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -15653,12 +15653,12 @@ static void test_bug27592() for (i= 0; i < NUM_ITERATIONS; i++) { - time_val.year=1000+lrand48()%1000; - time_val.month=1+lrand48()%12; - time_val.day=1+lrand48()%31; - time_val.hour=lrand48()%23; - time_val.minute=lrand48()%59; - time_val.second=lrand48()%59; + time_val.year= 2007; + time_val.month= 6; + time_val.day= 7; + time_val.hour= 18; + time_val.minute= 41; + time_val.second= 3; time_val.second_part=0; time_val.neg=0; From 60456bd8658342e12456618d3935a21aeebf9dfb Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 15 Jun 2007 10:59:40 -0600 Subject: [PATCH 4/6] manual merge --- tests/mysql_client_test.c | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index 0985b4d25ec..9bf062f7df8 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -15680,6 +15680,59 @@ static void test_bug28934() } +/* + Bug#27592 (stack overrun when storing datetime value using prepared statements) +*/ + +static void test_bug27592() +{ + const int NUM_ITERATIONS= 40; + int i; + int rc; + MYSQL_STMT *stmt= NULL; + MYSQL_BIND bind[1]; + MYSQL_TIME time_val; + + DBUG_ENTER("test_bug27592"); + myheader("test_bug27592"); + + mysql_query(mysql, "DROP TABLE IF EXISTS t1"); + mysql_query(mysql, "CREATE TABLE t1(c2 DATETIME)"); + + stmt= mysql_simple_prepare(mysql, "INSERT INTO t1 VALUES (?)"); + DIE_UNLESS(stmt); + + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type= MYSQL_TYPE_DATETIME; + bind[0].buffer= (char *) &time_val; + bind[0].length= NULL; + + for (i= 0; i < NUM_ITERATIONS; i++) + { + time_val.year= 2007; + time_val.month= 6; + time_val.day= 7; + time_val.hour= 18; + time_val.minute= 41; + time_val.second= 3; + + time_val.second_part=0; + time_val.neg=0; + + rc= mysql_stmt_bind_param(stmt, bind); + check_execute(stmt, rc); + + rc= mysql_stmt_execute(stmt); + check_execute(stmt, rc); + } + + mysql_stmt_close(stmt); + + DBUG_VOID_RETURN; +} + + /* Read and parse arguments and MySQL options from my.cnf */ @@ -15963,6 +16016,7 @@ static struct my_tests_st my_tests[]= { { "test_bug27876", test_bug27876 }, { "test_bug28505", test_bug28505 }, { "test_bug28934", test_bug28934 }, + { "test_bug27592", test_bug27592 }, { 0, 0 } }; From 5b013eb685a6f744b45c11b67e6ab3bef95661c8 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 15 Jun 2007 11:15:22 -0600 Subject: [PATCH 5/6] Fixed warnings raised from mysqltest (unknown -- commands) --- mysql-test/t/greedy_optimizer.test | 4 ++-- mysql-test/t/join.test | 6 ++++-- mysql-test/t/ndb_index_ordered.test | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/mysql-test/t/greedy_optimizer.test b/mysql-test/t/greedy_optimizer.test index 049d0ab09f7..b73f70c6a3e 100644 --- a/mysql-test/t/greedy_optimizer.test +++ b/mysql-test/t/greedy_optimizer.test @@ -145,11 +145,11 @@ select @@optimizer_prune_level; # # These are the values for the parameters that control the greedy optimizer # (total 6 combinations - 3 for optimizer_search_depth, 2 for optimizer_prune_level): --- +# 3: # set optimizer_search_depth=0; - automatic # set optimizer_search_depth=1; - min # set optimizer_search_depth=62; - max (default) --- +# 2: # set optimizer_prune_level=0 - exhaustive; # set optimizer_prune_level=1 - heuristic; # default diff --git a/mysql-test/t/join.test b/mysql-test/t/join.test index 68b97854c3b..5b599c3dad7 100644 --- a/mysql-test/t/join.test +++ b/mysql-test/t/join.test @@ -512,10 +512,12 @@ select * from v1a join (t3 natural join t4) on a = y; #-------------------------------------------------------------------- # Negative tests (tests for errors) #-------------------------------------------------------------------- +# works in Oracle - bug -- error 1052 -select * from t1 natural join (t3 cross join t4); -- works in Oracle - bug +select * from t1 natural join (t3 cross join t4); +# works in Oracle - bug -- error 1052 -select * from (t3 cross join t4) natural join t1; -- works in Oracle - bug +select * from (t3 cross join t4) natural join t1; -- error 1052 select * from t1 join (t2, t3) using (b); -- error 1052 diff --git a/mysql-test/t/ndb_index_ordered.test b/mysql-test/t/ndb_index_ordered.test index 19b024a043f..31385fd56b2 100644 --- a/mysql-test/t/ndb_index_ordered.test +++ b/mysql-test/t/ndb_index_ordered.test @@ -50,7 +50,7 @@ update t1 set c = 13 where b <= 3; select * from t1 order by a; update t1 set b = b + 1 where b > 4 and b < 7; select * from t1 order by a; --- Update primary key +# Update primary key update t1 set a = a + 10 where b > 1 and b < 7; select * from t1 order by a; From 32b6fbe5b21b3a8d2d74075c7a5d6383597de303 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 19 Jun 2007 12:03:10 +0200 Subject: [PATCH 6/6] try again. configure.in: Raise version number after cloning 5.0.44 --- configure.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.in b/configure.in index e7e9aa58c9b..5f02b5eae63 100644 --- a/configure.in +++ b/configure.in @@ -7,7 +7,7 @@ AC_INIT(sql/mysqld.cc) AC_CANONICAL_SYSTEM # The Docs Makefile.am parses this line! # remember to also change ndb version below and update version.c in ndb -AM_INIT_AUTOMAKE(mysql, 5.0.44) +AM_INIT_AUTOMAKE(mysql, 5.0.46) AM_CONFIG_HEADER(config.h) PROTOCOL_VERSION=10 @@ -23,7 +23,7 @@ NDB_SHARED_LIB_VERSION=$NDB_SHARED_LIB_MAJOR_VERSION:0:0 # ndb version NDB_VERSION_MAJOR=5 NDB_VERSION_MINOR=0 -NDB_VERSION_BUILD=44 +NDB_VERSION_BUILD=46 NDB_VERSION_STATUS="" # Set all version vars based on $VERSION. How do we do this more elegant ?