Commit ccbf81d5 authored by Evgeny Potemkin's avatar Evgeny Potemkin

Bug#57278: Crash on min/max + with date out of range.

MySQL officially supports DATE values starting from 1000-01-01. This is
enforced for int values, but not for string values, thus one
could easily insert '0001-01-01' value. Int values are checked by
number_to_datetime function and Item_cache_datetime::val_str uses it
to fill MYSQL_TIME struct out of cached int value. This leads to the
scenario where Item_cache_datetime caches a non-null datetime value and when
it tries to convert it from int to string number_to_datetime function
treats the value as out-of-range and returns an error and
Item_cache_datetime::val_str returns NULL for a non-null value. Due to this
inconsistency server crashes.

Now number_to_datetime allows DATE values below 1000-01-01 if the
TIME_FUZZY_DATE flag is set. Better NULL handling for Item_cache_datetime.
Added the Item_cache_datetime::store function to reset str_value_cached flag
when an item is stored.
parent 2a4f50a5
...@@ -318,4 +318,12 @@ f1 date YES NULL ...@@ -318,4 +318,12 @@ f1 date YES NULL
f2 date YES NULL f2 date YES NULL
DROP TABLE t1; DROP TABLE t1;
# #
#
# Bug#57278: Crash on min/max + with date out of range.
#
set @a=(select min(makedate('111','1'))) ;
select @a;
@a
0111-01-01
#
End of 6.0 tests End of 6.0 tests
...@@ -284,4 +284,12 @@ DROP TABLE t1; ...@@ -284,4 +284,12 @@ DROP TABLE t1;
--echo # --echo #
--echo #
--echo # Bug#57278: Crash on min/max + with date out of range.
--echo #
set @a=(select min(makedate('111','1'))) ;
select @a;
--echo #
--echo End of 6.0 tests --echo End of 6.0 tests
...@@ -1127,7 +1127,12 @@ longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res, ...@@ -1127,7 +1127,12 @@ longlong number_to_datetime(longlong nr, MYSQL_TIME *time_res,
nr= (nr+19000000L)*1000000L; /* YYMMDD, year: 1970-1999 */ nr= (nr+19000000L)*1000000L; /* YYMMDD, year: 1970-1999 */
goto ok; goto ok;
} }
if (nr < 10000101L) /*
Though officially we support DATE values from 1000-01-01 only, one can
easily insert a value like 1-1-1. So, for consistency reasons such dates
are allowed when TIME_FUZZY_DATE is set.
*/
if (nr < 10000101L && !(flags & TIME_FUZZY_DATE))
goto err; goto err;
if (nr <= 99991231L) if (nr <= 99991231L)
{ {
......
...@@ -7493,9 +7493,19 @@ void Item_cache_datetime::store(Item *item, longlong val_arg) ...@@ -7493,9 +7493,19 @@ void Item_cache_datetime::store(Item *item, longlong val_arg)
} }
void Item_cache_datetime::store(Item *item)
{
Item_cache::store(item);
str_value_cached= FALSE;
}
String *Item_cache_datetime::val_str(String *str) String *Item_cache_datetime::val_str(String *str)
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
if ((value_cached || str_value_cached) && null_value)
return NULL;
if (!str_value_cached) if (!str_value_cached)
{ {
/* /*
...@@ -7509,6 +7519,8 @@ String *Item_cache_datetime::val_str(String *str) ...@@ -7509,6 +7519,8 @@ String *Item_cache_datetime::val_str(String *str)
if (value_cached) if (value_cached)
{ {
MYSQL_TIME ltime; MYSQL_TIME ltime;
/* Return NULL in case of OOM/conversion error. */
null_value= TRUE;
if (str_value.alloc(MAX_DATE_STRING_REP_LENGTH)) if (str_value.alloc(MAX_DATE_STRING_REP_LENGTH))
return NULL; return NULL;
if (cached_field_type == MYSQL_TYPE_TIME) if (cached_field_type == MYSQL_TYPE_TIME)
...@@ -7531,13 +7543,14 @@ String *Item_cache_datetime::val_str(String *str) ...@@ -7531,13 +7543,14 @@ String *Item_cache_datetime::val_str(String *str)
{ {
int was_cut; int was_cut;
longlong res; longlong res;
res= number_to_datetime(val_int(), &ltime, TIME_FUZZY_DATE, &was_cut); res= number_to_datetime(int_value, &ltime, TIME_FUZZY_DATE, &was_cut);
if (res == -1) if (res == -1)
return NULL; return NULL;
} }
str_value.length(my_TIME_to_str(&ltime, str_value.length(my_TIME_to_str(&ltime,
const_cast<char*>(str_value.ptr()))); const_cast<char*>(str_value.ptr())));
str_value_cached= TRUE; str_value_cached= TRUE;
null_value= FALSE;
} }
else if (!cache_value()) else if (!cache_value())
return NULL; return NULL;
...@@ -7558,7 +7571,7 @@ my_decimal *Item_cache_datetime::val_decimal(my_decimal *decimal_val) ...@@ -7558,7 +7571,7 @@ my_decimal *Item_cache_datetime::val_decimal(my_decimal *decimal_val)
double Item_cache_datetime::val_real() double Item_cache_datetime::val_real()
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
if (!value_cached && !cache_value_int()) if ((!value_cached && !cache_value_int()) || null_value)
return 0.0; return 0.0;
return (double) int_value; return (double) int_value;
} }
...@@ -7566,7 +7579,7 @@ double Item_cache_datetime::val_real() ...@@ -7566,7 +7579,7 @@ double Item_cache_datetime::val_real()
longlong Item_cache_datetime::val_int() longlong Item_cache_datetime::val_int()
{ {
DBUG_ASSERT(fixed == 1); DBUG_ASSERT(fixed == 1);
if (!value_cached && !cache_value_int()) if ((!value_cached && !cache_value_int()) || null_value)
return 0; return 0;
return int_value; return int_value;
} }
......
...@@ -3451,8 +3451,8 @@ class Item_cache_datetime: public Item_cache ...@@ -3451,8 +3451,8 @@ class Item_cache_datetime: public Item_cache
cmp_context= STRING_RESULT; cmp_context= STRING_RESULT;
} }
virtual void store(Item *item) { Item_cache::store(item); }
void store(Item *item, longlong val_arg); void store(Item *item, longlong val_arg);
void store(Item *item);
double val_real(); double val_real();
longlong val_int(); longlong val_int();
String* val_str(String *str); String* val_str(String *str);
......
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