mirror of
https://github.com/MariaDB/server.git
synced 2025-01-28 17:54:16 +01:00
Fix for bug #6266 "Invalid DATETIME value is not handled properly".
In server we assume that datetime values stored in MYSQL_TIME struct are normalized (and year is not greater than 9999), so we should perform range checks in all places then we convert something to MYSQL_TIME. include/my_time.h: Added one more argument to set_zero_time() function to make it more convinient. Added comment clarifying why MAX_DATE_STRING_REP_LENGTH value is 30. include/mysql_time.h: Documented MySQL's internal assumptions for members of MYSQL_TIME structure. libmysql/libmysql.c: It does not make sense to set MYSQL_TIME::time_type twice in case of errors. mysql-test/r/type_datetime.result: Added test for bug #6266 "Invalid DATETIME value not handled properly". mysql-test/t/type_datetime.test: Added test for bug #6266 "Invalid DATETIME value not handled properly". sql-common/my_time.c: str_to_datetime(): Added missing check for too big year values. set_zero_time(): added time_type argument, since MYSQL_TIMESTAMP_NONE is not the value that we want in most cases. sql/field.cc: Field_datetime::store_time(): clarified why we don't perform any range checks here. sql/item.cc: Item_param::set_time(): Added comment describing this method and range checking for TIME values. sql/sql_prepare.cc: Removed comments about range checking for TIME values in prepared statements, which are no longer true. set_zero_time() has one more argument now. tests/client_test.c: Added test for bug #6266 "Invalid DATETIME value not handled properly"
This commit is contained in:
parent
7fd0cc2d12
commit
5d9f7edd6d
10 changed files with 209 additions and 34 deletions
|
@ -58,14 +58,15 @@ void init_time(void);
|
|||
my_time_t
|
||||
my_system_gmt_sec(const MYSQL_TIME *t, long *my_timezone, bool *in_dst_time_gap);
|
||||
|
||||
void set_zero_time(MYSQL_TIME *tm);
|
||||
void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type);
|
||||
|
||||
/*
|
||||
Required buffer length for my_time_to_str, my_date_to_str,
|
||||
my_datetime_to_str and TIME_to_string functions. Note, that the
|
||||
caller is still responsible to check that given TIME structure
|
||||
has values in valid ranges, otherwise size of the buffer could
|
||||
be not enough.
|
||||
be not enough. We also rely on the fact that even wrong values
|
||||
sent using binary protocol fit in this buffer.
|
||||
*/
|
||||
#define MAX_DATE_STRING_REP_LENGTH 30
|
||||
|
||||
|
|
|
@ -33,6 +33,18 @@ enum enum_mysql_timestamp_type
|
|||
};
|
||||
|
||||
|
||||
/*
|
||||
Structure which is used to represent datetime values inside MySQL.
|
||||
|
||||
We assume that values in this structure are normalized, i.e. year <= 9999,
|
||||
month <= 12, day <= 31, hour <= 23, hour <= 59, hour <= 59. Many functions
|
||||
in server such as my_system_gmt_sec() or make_time() family of functions
|
||||
rely on this (actually now usage of make_*() family relies on a bit weaker
|
||||
restriction). Also functions that produce MYSQL_TIME as result ensure this.
|
||||
There is one exception to this rule though if this structure holds time
|
||||
value (time_type == MYSQL_TIMESTAMP_TIME) days and hour member can hold
|
||||
bigger values.
|
||||
*/
|
||||
typedef struct st_mysql_time
|
||||
{
|
||||
unsigned int year, month, day, hour, minute, second;
|
||||
|
|
|
@ -3256,11 +3256,12 @@ static void read_binary_time(MYSQL_TIME *tm, uchar **pos)
|
|||
tm->hour+= tm->day*24;
|
||||
tm->day= 0;
|
||||
}
|
||||
tm->time_type= MYSQL_TIMESTAMP_TIME;
|
||||
|
||||
*pos+= length;
|
||||
}
|
||||
else
|
||||
set_zero_time(tm);
|
||||
tm->time_type= MYSQL_TIMESTAMP_TIME;
|
||||
set_zero_time(tm, MYSQL_TIMESTAMP_TIME);
|
||||
}
|
||||
|
||||
static void read_binary_datetime(MYSQL_TIME *tm, uchar **pos)
|
||||
|
@ -3285,12 +3286,12 @@ static void read_binary_datetime(MYSQL_TIME *tm, uchar **pos)
|
|||
else
|
||||
tm->hour= tm->minute= tm->second= 0;
|
||||
tm->second_part= (length > 7) ? (ulong) sint4korr(to+7) : 0;
|
||||
tm->time_type= MYSQL_TIMESTAMP_DATETIME;
|
||||
|
||||
*pos+= length;
|
||||
}
|
||||
else
|
||||
set_zero_time(tm);
|
||||
tm->time_type= MYSQL_TIMESTAMP_DATETIME;
|
||||
set_zero_time(tm, MYSQL_TIMESTAMP_DATETIME);
|
||||
}
|
||||
|
||||
static void read_binary_date(MYSQL_TIME *tm, uchar **pos)
|
||||
|
@ -3307,12 +3308,12 @@ static void read_binary_date(MYSQL_TIME *tm, uchar **pos)
|
|||
tm->hour= tm->minute= tm->second= 0;
|
||||
tm->second_part= 0;
|
||||
tm->neg= 0;
|
||||
tm->time_type= MYSQL_TIMESTAMP_DATE;
|
||||
|
||||
*pos+= length;
|
||||
}
|
||||
else
|
||||
set_zero_time(tm);
|
||||
tm->time_type= MYSQL_TIMESTAMP_DATE;
|
||||
set_zero_time(tm, MYSQL_TIMESTAMP_DATE);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -97,13 +97,15 @@ select * from t1 where a is null or b is null;
|
|||
a b
|
||||
drop table t1;
|
||||
create table t1 (t datetime);
|
||||
insert into t1 values (20030102030460),(20030102036301),(20030102240401),(20030132030401),(20031302030460);
|
||||
insert into t1 values (20030102030460),(20030102036301),(20030102240401),
|
||||
(20030132030401),(20031302030401),(100001202030401);
|
||||
Warnings:
|
||||
Warning 1265 Data truncated for column 't' at row 1
|
||||
Warning 1265 Data truncated for column 't' at row 2
|
||||
Warning 1265 Data truncated for column 't' at row 3
|
||||
Warning 1265 Data truncated for column 't' at row 4
|
||||
Warning 1265 Data truncated for column 't' at row 5
|
||||
Warning 1265 Data truncated for column 't' at row 6
|
||||
select * from t1;
|
||||
t
|
||||
0000-00-00 00:00:00
|
||||
|
@ -111,14 +113,18 @@ t
|
|||
0000-00-00 00:00:00
|
||||
0000-00-00 00:00:00
|
||||
0000-00-00 00:00:00
|
||||
0000-00-00 00:00:00
|
||||
delete from t1;
|
||||
insert into t1 values ("20030102030460"),("20030102036301"),("20030102240401"),("20030132030401"),("20031302030460");
|
||||
insert into t1 values
|
||||
("2003-01-02 03:04:60"),("2003-01-02 03:63:01"),("2003-01-02 24:04:01"),
|
||||
("2003-01-32 03:04:01"),("2003-13-02 03:04:01"), ("10000-12-02 03:04:00");
|
||||
Warnings:
|
||||
Warning 1264 Data truncated; out of range for column 't' at row 1
|
||||
Warning 1264 Data truncated; out of range for column 't' at row 2
|
||||
Warning 1264 Data truncated; out of range for column 't' at row 3
|
||||
Warning 1264 Data truncated; out of range for column 't' at row 4
|
||||
Warning 1264 Data truncated; out of range for column 't' at row 5
|
||||
Warning 1264 Data truncated; out of range for column 't' at row 6
|
||||
select * from t1;
|
||||
t
|
||||
0000-00-00 00:00:00
|
||||
|
@ -126,6 +132,7 @@ t
|
|||
0000-00-00 00:00:00
|
||||
0000-00-00 00:00:00
|
||||
0000-00-00 00:00:00
|
||||
0000-00-00 00:00:00
|
||||
delete from t1;
|
||||
insert into t1 values ("0000-00-00 00:00:00 some trailer"),("2003-01-01 00:00:00 some trailer");
|
||||
Warnings:
|
||||
|
|
|
@ -77,10 +77,13 @@ drop table t1;
|
|||
# warnings (for both strings and numbers)
|
||||
#
|
||||
create table t1 (t datetime);
|
||||
insert into t1 values (20030102030460),(20030102036301),(20030102240401),(20030132030401),(20031302030460);
|
||||
insert into t1 values (20030102030460),(20030102036301),(20030102240401),
|
||||
(20030132030401),(20031302030401),(100001202030401);
|
||||
select * from t1;
|
||||
delete from t1;
|
||||
insert into t1 values ("20030102030460"),("20030102036301"),("20030102240401"),("20030132030401"),("20031302030460");
|
||||
insert into t1 values
|
||||
("2003-01-02 03:04:60"),("2003-01-02 03:63:01"),("2003-01-02 24:04:01"),
|
||||
("2003-01-32 03:04:01"),("2003-13-02 03:04:01"), ("10000-12-02 03:04:00");
|
||||
select * from t1;
|
||||
delete from t1;
|
||||
insert into t1 values ("0000-00-00 00:00:00 some trailer"),("2003-01-01 00:00:00 some trailer");
|
||||
|
|
|
@ -343,7 +343,8 @@ str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,
|
|||
(l_time->month || l_time->day))
|
||||
l_time->year+= (l_time->year < YY_PART_YEAR ? 2000 : 1900);
|
||||
|
||||
if (number_of_fields < 3 || l_time->month > 12 ||
|
||||
if (number_of_fields < 3 ||
|
||||
l_time->year > 9999 || l_time->month > 12 ||
|
||||
l_time->day > 31 || l_time->hour > 23 ||
|
||||
l_time->minute > 59 || l_time->second > 59 ||
|
||||
(!(flags & TIME_FUZZY_DATE) && (l_time->month == 0 || l_time->day == 0)))
|
||||
|
@ -720,10 +721,10 @@ my_system_gmt_sec(const MYSQL_TIME *t, long *my_timezone, bool *in_dst_time_gap)
|
|||
|
||||
/* Set MYSQL_TIME structure to 0000-00-00 00:00:00.000000 */
|
||||
|
||||
void set_zero_time(MYSQL_TIME *tm)
|
||||
void set_zero_time(MYSQL_TIME *tm, enum enum_mysql_timestamp_type time_type)
|
||||
{
|
||||
bzero((void*) tm, sizeof(*tm));
|
||||
tm->time_type= MYSQL_TIMESTAMP_NONE;
|
||||
tm->time_type= time_type;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4086,6 +4086,10 @@ int Field_datetime::store(longlong nr)
|
|||
void Field_datetime::store_time(TIME *ltime,timestamp_type type)
|
||||
{
|
||||
longlong tmp;
|
||||
/*
|
||||
We don't perform range checking here since values stored in TIME
|
||||
structure always fit into DATETIME range.
|
||||
*/
|
||||
if (type == MYSQL_TIMESTAMP_DATE || type == MYSQL_TIMESTAMP_DATETIME)
|
||||
tmp=((ltime->year*10000L+ltime->month*100+ltime->day)*LL(1000000)+
|
||||
(ltime->hour*10000L+ltime->minute*100+ltime->second));
|
||||
|
|
26
sql/item.cc
26
sql/item.cc
|
@ -837,6 +837,21 @@ void Item_param::set_double(double d)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
Set parameter value from TIME value.
|
||||
|
||||
SYNOPSIS
|
||||
set_time()
|
||||
tm - datetime value to set (time_type is ignored)
|
||||
type - type of datetime value
|
||||
max_length_arg - max length of datetime value as string
|
||||
|
||||
NOTE
|
||||
If we value to be stored is not normalized, zero value will be stored
|
||||
instead and proper warning will be produced. This function relies on
|
||||
the fact that even wrong value sent over binary protocol fits into
|
||||
MAX_DATE_STRING_REP_LENGTH buffer.
|
||||
*/
|
||||
void Item_param::set_time(TIME *tm, timestamp_type type, uint32 max_length_arg)
|
||||
{
|
||||
DBUG_ENTER("Item_param::set_time");
|
||||
|
@ -844,6 +859,17 @@ void Item_param::set_time(TIME *tm, timestamp_type type, uint32 max_length_arg)
|
|||
value.time= *tm;
|
||||
value.time.time_type= type;
|
||||
|
||||
if (value.time.year > 9999 || value.time.month > 12 ||
|
||||
value.time.day > 31 ||
|
||||
type != MYSQL_TIMESTAMP_TIME && value.time.hour > 23 ||
|
||||
value.time.minute > 59 || value.time.second > 59)
|
||||
{
|
||||
char buff[MAX_DATE_STRING_REP_LENGTH];
|
||||
uint length= my_TIME_to_str(&value.time, buff);
|
||||
make_truncated_value_warning(current_thd, buff, length, type);
|
||||
set_zero_time(&value.time, MYSQL_TIMESTAMP_ERROR);
|
||||
}
|
||||
|
||||
state= TIME_VALUE;
|
||||
maybe_null= 0;
|
||||
max_length= max_length_arg;
|
||||
|
|
|
@ -349,12 +349,6 @@ static void set_param_time(Item_param *param, uchar **pos, ulong len)
|
|||
|
||||
tm.neg= (bool) to[0];
|
||||
day= (uint) sint4korr(to+1);
|
||||
/*
|
||||
Note, that though ranges of hour, minute and second are not checked
|
||||
here we rely on them being < 256: otherwise
|
||||
we'll get buffer overflow in make_{date,time} functions,
|
||||
which are called when time value is converted to string.
|
||||
*/
|
||||
tm.hour= (uint) to[5] + day * 24;
|
||||
tm.minute= (uint) to[6];
|
||||
tm.second= (uint) to[7];
|
||||
|
@ -369,7 +363,7 @@ static void set_param_time(Item_param *param, uchar **pos, ulong len)
|
|||
tm.day= tm.year= tm.month= 0;
|
||||
}
|
||||
else
|
||||
set_zero_time(&tm);
|
||||
set_zero_time(&tm, MYSQL_TIMESTAMP_TIME);
|
||||
param->set_time(&tm, MYSQL_TIMESTAMP_TIME,
|
||||
MAX_TIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN);
|
||||
*pos+= length;
|
||||
|
@ -388,11 +382,6 @@ static void set_param_datetime(Item_param *param, uchar **pos, ulong len)
|
|||
tm.year= (uint) sint2korr(to);
|
||||
tm.month= (uint) to[2];
|
||||
tm.day= (uint) to[3];
|
||||
/*
|
||||
Note, that though ranges of hour, minute and second are not checked
|
||||
here we rely on them being < 256: otherwise
|
||||
we'll get buffer overflow in make_{date,time} functions.
|
||||
*/
|
||||
if (length > 4)
|
||||
{
|
||||
tm.hour= (uint) to[4];
|
||||
|
@ -405,7 +394,7 @@ static void set_param_datetime(Item_param *param, uchar **pos, ulong len)
|
|||
tm.second_part= (length > 7) ? (ulong) sint4korr(to+7) : 0;
|
||||
}
|
||||
else
|
||||
set_zero_time(&tm);
|
||||
set_zero_time(&tm, MYSQL_TIMESTAMP_DATETIME);
|
||||
param->set_time(&tm, MYSQL_TIMESTAMP_DATETIME,
|
||||
MAX_DATETIME_WIDTH * MY_CHARSET_BIN_MB_MAXLEN);
|
||||
*pos+= length;
|
||||
|
@ -419,11 +408,7 @@ static void set_param_date(Item_param *param, uchar **pos, ulong len)
|
|||
if (length >= 4)
|
||||
{
|
||||
uchar *to= *pos;
|
||||
/*
|
||||
Note, that though ranges of hour, minute and second are not checked
|
||||
here we rely on them being < 256: otherwise
|
||||
we'll get buffer overflow in make_{date,time} functions.
|
||||
*/
|
||||
|
||||
tm.year= (uint) sint2korr(to);
|
||||
tm.month= (uint) to[2];
|
||||
tm.day= (uint) to[3];
|
||||
|
@ -433,7 +418,7 @@ static void set_param_date(Item_param *param, uchar **pos, ulong len)
|
|||
tm.neg= 0;
|
||||
}
|
||||
else
|
||||
set_zero_time(&tm);
|
||||
set_zero_time(&tm, MYSQL_TIMESTAMP_DATE);
|
||||
param->set_time(&tm, MYSQL_TIMESTAMP_DATE,
|
||||
MAX_DATE_WIDTH * MY_CHARSET_BIN_MB_MAXLEN);
|
||||
*pos+= length;
|
||||
|
|
|
@ -11088,6 +11088,139 @@ static void test_bug6096()
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
Test of basic checks that are performed in server for components
|
||||
of MYSQL_TIME parameters.
|
||||
*/
|
||||
static void test_datetime_ranges()
|
||||
{
|
||||
const char *stmt_text;
|
||||
int rc, i;
|
||||
MYSQL_STMT *stmt;
|
||||
MYSQL_BIND bind[6];
|
||||
MYSQL_TIME tm[6];
|
||||
|
||||
myheader("test_datetime_ranges");
|
||||
|
||||
stmt_text= "drop table if exists t1";
|
||||
rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
|
||||
myquery(rc);
|
||||
|
||||
stmt_text= "create table t1 (year datetime, month datetime, day datetime, "
|
||||
"hour datetime, min datetime, sec datetime)";
|
||||
rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
|
||||
myquery(rc);
|
||||
|
||||
stmt= mysql_simple_prepare(mysql,
|
||||
"INSERT INTO t1 VALUES (?, ?, ?, ?, ?, ?)");
|
||||
check_stmt(stmt);
|
||||
verify_param_count(stmt, 6);
|
||||
|
||||
bzero(bind, sizeof(bind));
|
||||
for (i= 0; i < 6; i++)
|
||||
{
|
||||
bind[i].buffer_type= MYSQL_TYPE_DATETIME;
|
||||
bind[i].buffer= &tm[i];
|
||||
}
|
||||
rc= mysql_stmt_bind_param(stmt, bind);
|
||||
check_execute(stmt, rc);
|
||||
|
||||
tm[0].year= 2004; tm[0].month= 11; tm[0].day= 10;
|
||||
tm[0].hour= 12; tm[0].minute= 30; tm[0].second= 30;
|
||||
tm[0].second_part= 0; tm[0].neg= 0;
|
||||
|
||||
tm[5]= tm[4]= tm[3]= tm[2]= tm[1]= tm[0];
|
||||
tm[0].year= 10000; tm[1].month= 13; tm[2].day= 32;
|
||||
tm[3].hour= 24; tm[4].minute= 60; tm[5].second= 60;
|
||||
|
||||
rc= mysql_stmt_execute(stmt);
|
||||
check_execute(stmt, rc);
|
||||
DIE_UNLESS(mysql_warning_count(mysql) != 6);
|
||||
|
||||
verify_col_data("t1", "year", "0000-00-00 00:00:00");
|
||||
verify_col_data("t1", "month", "0000-00-00 00:00:00");
|
||||
verify_col_data("t1", "day", "0000-00-00 00:00:00");
|
||||
verify_col_data("t1", "hour", "0000-00-00 00:00:00");
|
||||
verify_col_data("t1", "min", "0000-00-00 00:00:00");
|
||||
verify_col_data("t1", "sec", "0000-00-00 00:00:00");
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
|
||||
stmt_text= "delete from t1";
|
||||
rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
|
||||
myquery(rc);
|
||||
|
||||
stmt= mysql_simple_prepare(mysql, "INSERT INTO t1 (year, month, day) "
|
||||
"VALUES (?, ?, ?)");
|
||||
check_stmt(stmt);
|
||||
verify_param_count(stmt, 3);
|
||||
|
||||
/*
|
||||
We reuse contents of bind and tm arrays left from previous part of test.
|
||||
*/
|
||||
for (i= 0; i < 3; i++)
|
||||
bind[i].buffer_type= MYSQL_TYPE_DATE;
|
||||
|
||||
rc= mysql_stmt_bind_param(stmt, bind);
|
||||
check_execute(stmt, rc);
|
||||
|
||||
rc= mysql_stmt_execute(stmt);
|
||||
check_execute(stmt, rc);
|
||||
DIE_UNLESS(mysql_warning_count(mysql) != 3);
|
||||
|
||||
verify_col_data("t1", "year", "0000-00-00 00:00:00");
|
||||
verify_col_data("t1", "month", "0000-00-00 00:00:00");
|
||||
verify_col_data("t1", "day", "0000-00-00 00:00:00");
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
|
||||
stmt_text= "drop table t1";
|
||||
rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
|
||||
myquery(rc);
|
||||
|
||||
stmt_text= "create table t1 (day_ovfl time, day time, hour time, min time, sec time)";
|
||||
rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
|
||||
myquery(rc);
|
||||
|
||||
stmt= mysql_simple_prepare(mysql,
|
||||
"INSERT INTO t1 VALUES (?, ?, ?, ?, ?)");
|
||||
check_stmt(stmt);
|
||||
verify_param_count(stmt, 5);
|
||||
|
||||
/*
|
||||
Again we reuse what we can from previous part of test.
|
||||
*/
|
||||
for (i= 0; i < 5; i++)
|
||||
bind[i].buffer_type= MYSQL_TYPE_TIME;
|
||||
|
||||
rc= mysql_stmt_bind_param(stmt, bind);
|
||||
check_execute(stmt, rc);
|
||||
|
||||
tm[0].year= 0; tm[0].month= 0; tm[0].day= 10;
|
||||
tm[0].hour= 12; tm[0].minute= 30; tm[0].second= 30;
|
||||
tm[0].second_part= 0; tm[0].neg= 0;
|
||||
|
||||
tm[4]= tm[3]= tm[2]= tm[1]= tm[0];
|
||||
tm[0].day= 35; tm[1].day= 34; tm[2].hour= 30; tm[3].minute= 60; tm[4].second= 60;
|
||||
|
||||
rc= mysql_stmt_execute(stmt);
|
||||
check_execute(stmt, rc);
|
||||
DIE_UNLESS(mysql_warning_count(mysql) != 2);
|
||||
|
||||
verify_col_data("t1", "day_ovfl", "838:59:59");
|
||||
verify_col_data("t1", "day", "828:30:30");
|
||||
verify_col_data("t1", "hour", "270:30:30");
|
||||
verify_col_data("t1", "min", "00:00:00");
|
||||
verify_col_data("t1", "sec", "00:00:00");
|
||||
|
||||
mysql_stmt_close(stmt);
|
||||
|
||||
stmt_text= "drop table t1";
|
||||
rc= mysql_real_query(mysql, stmt_text, strlen(stmt_text));
|
||||
myquery(rc);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Read and parse arguments and MySQL options from my.cnf
|
||||
*/
|
||||
|
@ -11404,6 +11537,8 @@ int main(int argc, char **argv)
|
|||
test_bug6046(); /* NATURAL JOIN transformation works in PS */
|
||||
test_bug6081(); /* test of mysql_create_db()/mysql_rm_db() */
|
||||
test_bug6096(); /* max_length for numeric columns */
|
||||
test_datetime_ranges(); /* Test if basic checks are performed for
|
||||
components of MYSQL_TIME parameters */
|
||||
/*
|
||||
XXX: PLEASE RUN THIS PROGRAM UNDER VALGRIND AND VERIFY THAT YOUR TEST
|
||||
DOESN'T CONTAIN WARNINGS/ERRORS BEFORE YOU PUSH.
|
||||
|
|
Loading…
Add table
Reference in a new issue