From 634d3ff2c658902fa3ed4eb0f988a79564cfe94e Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 4 Oct 2006 17:13:32 +0400 Subject: Fixes a number of problems with time/datetime <-> string conversion functions: - bug #11655 "Wrong time is returning from nested selects - maximum time exists - input and output TIME values were not validated properly in several conversion functions - bug #20927 "sec_to_time treats big unsigned as signed" - integer overflows were not checked in several functions. As a result, input values like 2^32 or 3600*2^32 were treated as 0 - BIGINT UNSIGNED values were treated as SIGNED in several functions - in cases where both input string truncation and out-of-range TIME value occur, only 'truncated incorrect time value' warning was produced include/my_time.h: Added defines for the TIME limits Added defines for the warning flags set by str_to_time() and check_time_range() Added check_time_range() declaration mysql-test/r/func_sapdb.result: Fixed testcases which relied on incorrect TIMEDIFF() behaviour mysql-test/r/func_time.result: Fixed testcase which relied on incorrect behaviour Added testcases for out-of-range values in SEC_TO_TIME(), TIME_TO_SEC(), ADDTIME(), SUBTIME() and EXTRACT() mysql-test/t/func_time.test: Added testcases for out-of-range values in SEC_TO_TIME(), TIME_TO_SEC(), ADDTIME(), SUBTIME() and EXTRACT() sql-common/my_time.c: Added check_time_range() to be used from str_to_time() and item_timefunc.cc Added new out-of-range flag to str_to_time() warnings Use '%u' instead of '%d' in my_*_to_str() because the arguments are unsigned sql/field.cc: Replaced out-of-range checks with checks for flags returned by str_to_time() sql/item_timefunc.cc: Added wrappers over make_datetime() and make_time() which perform out-of-range checks on input values Moved common code in Item_func_sec_to_time::val_str() and Item_func_sec_to_time::val_int() into a separate function sec_to_time() Replaced calls to make_datetime() with make_datetime_with_warn() in Item_func_add_time and Item_func_timediff Checks for 'unsigned int' overflows in Item_func_maketime Use make_time_with_warn() instead of make_time() in Item_func_maketime Fixed incorrect sizeof() in Item_func_str_to_date::get_time() sql/time.cc: Check for return value of str_to_time() along with warning flags --- sql-common/my_time.c | 91 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 22 deletions(-) (limited to 'sql-common/my_time.c') diff --git a/sql-common/my_time.c b/sql-common/my_time.c index 3c46a944ba9..baf9a3902d9 100644 --- a/sql-common/my_time.c +++ b/sql-common/my_time.c @@ -402,8 +402,10 @@ err: There may be an optional [.second_part] after seconds length Length of str l_time Store result here - was_cut Set to 1 if value was cut during conversion or to 0 - otherwise. + warning Set MYSQL_TIME_WARN_TRUNCATED flag if the input string + was cut during conversion, and/or + MYSQL_TIME_WARN_OUT_OF_RANGE flag, if the value is + out of range. NOTES Because of the extra days argument, this function can only @@ -414,16 +416,16 @@ err: 1 error */ -bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time, - int *was_cut) +bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time, int *warning) { - long date[5],value; + ulong date[5]; + ulonglong value; const char *end=str+length, *end_of_days; bool found_days,found_hours; uint state; l_time->neg=0; - *was_cut= 0; + *warning= 0; for (; str != end && my_isspace(&my_charset_latin1,*str) ; str++) length--; if (str != end && *str == '-') @@ -438,13 +440,16 @@ bool str_to_time(const char *str, uint length, MYSQL_TIME *l_time, /* Check first if this is a full TIMESTAMP */ if (length >= 12) { /* Probably full timestamp */ + int was_cut; enum enum_mysql_timestamp_type res= str_to_datetime(str, length, l_time, - (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), was_cut); + (TIME_FUZZY_DATE | TIME_DATETIME_ONLY), &was_cut); if ((int) res >= (int) MYSQL_TIMESTAMP_ERROR) + { + if (was_cut) + *warning|= MYSQL_TIME_WARN_TRUNCATED; return res == MYSQL_TIMESTAMP_ERROR; - /* We need to restore was_cut flag since str_to_datetime can modify it */ - *was_cut= 0; + } } /* Not a timestamp. Try to get this as a DAYS_TO_SECOND string */ @@ -524,7 +529,7 @@ fractional: if (field_length > 0) value*= (long) log_10_int[field_length]; else if (field_length < 0) - *was_cut= 1; + *warning|= MYSQL_TIME_WARN_TRUNCATED; date[4]=value; } else @@ -538,10 +543,7 @@ fractional: ((str[1] == '-' || str[1] == '+') && (end - str) > 2 && my_isdigit(&my_charset_latin1, str[2])))) - { - *was_cut= 1; return 1; - } if (internal_format_positions[7] != 255) { @@ -560,12 +562,12 @@ fractional: } } - /* Some simple checks */ - if (date[2] >= 60 || date[3] >= 60) - { - *was_cut= 1; + /* Integer overflow checks */ + if (date[0] > UINT_MAX || date[1] > UINT_MAX || + date[2] > UINT_MAX || date[3] > UINT_MAX || + date[4] > UINT_MAX) return 1; - } + l_time->year= 0; /* For protocol::store_time */ l_time->month= 0; l_time->day= date[0]; @@ -575,6 +577,10 @@ fractional: l_time->second_part= date[4]; l_time->time_type= MYSQL_TIMESTAMP_TIME; + /* Check if the value is valid and fits into TIME range */ + if (check_time_range(l_time, warning)) + return 1; + /* Check if there is garbage at end of the TIME specification */ if (str != end) { @@ -582,7 +588,7 @@ fractional: { if (!my_isspace(&my_charset_latin1,*str)) { - *was_cut= 1; + *warning|= MYSQL_TIME_WARN_TRUNCATED; break; } } while (++str != end); @@ -591,6 +597,47 @@ fractional: } +/* + Check 'time' value to lie in the TIME range + + SYNOPSIS: + check_time_range() + time pointer to TIME value + warning set MYSQL_TIME_WARN_OUT_OF_RANGE flag if the value is out of range + + DESCRIPTION + If the time value lies outside of the range [-838:59:59, 838:59:59], + set it to the closest endpoint of the range and set + MYSQL_TIME_WARN_OUT_OF_RANGE flag in the 'warning' variable. + + RETURN + 0 time value is valid, but was possibly truncated + 1 time value is invalid +*/ + +int check_time_range(struct st_mysql_time *time, int *warning) +{ + longlong hour; + + if (time->minute >= 60 || time->second >= 60) + return 1; + + hour= time->hour + (24*time->day); + if (hour <= TIME_MAX_HOUR && + (hour != TIME_MAX_HOUR || time->minute != TIME_MAX_MINUTE || + time->second != TIME_MAX_SECOND || !time->second_part)) + return 0; + + time->day= 0; + time->hour= TIME_MAX_HOUR; + time->minute= TIME_MAX_MINUTE; + time->second= TIME_MAX_SECOND; + time->second_part= 0; + *warning|= MYSQL_TIME_WARN_OUT_OF_RANGE; + return 0; +} + + /* Prepare offset of system time zone from UTC for my_system_gmt_sec() func. @@ -776,7 +823,7 @@ void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type) int my_time_to_str(const MYSQL_TIME *l_time, char *to) { uint extra_hours= 0; - return my_sprintf(to, (to, "%s%02d:%02d:%02d", + return my_sprintf(to, (to, "%s%02u:%02u:%02u", (l_time->neg ? "-" : ""), extra_hours+ l_time->hour, l_time->minute, @@ -785,7 +832,7 @@ int my_time_to_str(const MYSQL_TIME *l_time, char *to) int my_date_to_str(const MYSQL_TIME *l_time, char *to) { - return my_sprintf(to, (to, "%04d-%02d-%02d", + return my_sprintf(to, (to, "%04u-%02u-%02u", l_time->year, l_time->month, l_time->day)); @@ -793,7 +840,7 @@ int my_date_to_str(const MYSQL_TIME *l_time, char *to) int my_datetime_to_str(const MYSQL_TIME *l_time, char *to) { - return my_sprintf(to, (to, "%04d-%02d-%02d %02d:%02d:%02d", + return my_sprintf(to, (to, "%04u-%02u-%02u %02u:%02u:%02u", l_time->year, l_time->month, l_time->day, -- cgit v1.2.1