Skip to content

Commit 7925326

Browse files
committed
MDEV-30931 UBSAN: negation of -X cannot be represented in type 'long long int'; cast to an unsigned type to negate this value to itself in get_interval_value on SELECT
- Fixing the code in get_interval_value() to use Longlong_hybrid_null. This allows to handle correctly: - Signed and unsigned arguments (the old code assumed the argument to be signed) - Avoid undefined negation behavior the corner case with LONGLONG_MIN This fixes the UBSAN warning: negation of -9223372036854775808 cannot be represented in type 'long long int'; - Fixing the code in get_interval_value() to avoid overflow in the INTERVAL_QUARTER and INTERVAL_WEEK branches. This fixes the UBSAN warning: signed integer overflow: -9223372036854775808 * 7 cannot be represented in type 'long long int' - Fixing the INTERVAL_WEEK branch in date_add_interval() to handle huge numbers correctly. Before the change, huge positive numeber were treated as their negative complements. Note, some other branches still can be affected by this problem and should also be fixed eventually.
1 parent 44b23bb commit 7925326

File tree

5 files changed

+83
-17
lines changed

5 files changed

+83
-17
lines changed

mysql-test/main/func_date_add.result

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,34 @@ select 30 + (20010101 + interval 2 day), x from v1;
200200
20010133 20010133
201201
drop view v1;
202202
End of 10.2 tests
203+
#
204+
# Start of 10.5 tests
205+
#
206+
#
207+
# MDEV-30931 UBSAN: negation of -X cannot be represented in type 'long long int'; cast to an unsigned type to negate this value to itself in get_interval_value on SELECT
208+
#
209+
SELECT DATE_ADD('01-01-23',INTERVAL '9223372036854775808-02' WEEK);
210+
DATE_ADD('01-01-23',INTERVAL '9223372036854775808-02' WEEK)
211+
NULL
212+
Warnings:
213+
Warning 1292 Truncated incorrect INTEGER value: '9223372036854775808-02'
214+
Warning 1441 Datetime function: datetime field overflow
215+
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775807 WEEK);
216+
DATE_ADD('01-01-23',INTERVAL -9223372036854775807 WEEK)
217+
NULL
218+
Warnings:
219+
Warning 1441 Datetime function: datetime field overflow
220+
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775808 WEEK);
221+
DATE_ADD('01-01-23',INTERVAL -9223372036854775808 WEEK)
222+
NULL
223+
Warnings:
224+
Warning 1441 Datetime function: datetime field overflow
225+
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775809 WEEK);
226+
DATE_ADD('01-01-23',INTERVAL -9223372036854775809 WEEK)
227+
NULL
228+
Warnings:
229+
Warning 1916 Got overflow when converting '-9223372036854775809' to INT. Value truncated
230+
Warning 1441 Datetime function: datetime field overflow
231+
#
232+
# End of 10.5 tests
233+
#

mysql-test/main/func_date_add.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,20 @@ select 30 + (20010101 + interval 2 day), x from v1;
169169
drop view v1;
170170

171171
--echo End of 10.2 tests
172+
173+
--echo #
174+
--echo # Start of 10.5 tests
175+
--echo #
176+
177+
--echo #
178+
--echo # MDEV-30931 UBSAN: negation of -X cannot be represented in type 'long long int'; cast to an unsigned type to negate this value to itself in get_interval_value on SELECT
179+
--echo #
180+
181+
SELECT DATE_ADD('01-01-23',INTERVAL '9223372036854775808-02' WEEK);
182+
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775807 WEEK);
183+
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775808 WEEK);
184+
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775809 WEEK);
185+
186+
--echo #
187+
--echo # End of 10.5 tests
188+
--echo #

sql/item_timefunc.cc

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,15 @@ my_decimal *Item_func_time_to_sec::decimal_op(my_decimal* buf)
13061306
}
13071307

13081308

1309+
static inline
1310+
uint32 adjust_interval_field_uint32(ulonglong value, int32 multiplier)
1311+
{
1312+
return value > ((ulonglong) (uint32) (UINT_MAX32)) / multiplier ?
1313+
(uint32) UINT_MAX32 :
1314+
(uint32) (value * multiplier);
1315+
}
1316+
1317+
13091318
/**
13101319
Convert a string to a interval value.
13111320
@@ -1316,7 +1325,7 @@ bool get_interval_value(THD *thd, Item *args,
13161325
interval_type int_type, INTERVAL *interval)
13171326
{
13181327
ulonglong array[5];
1319-
longlong UNINIT_VAR(value);
1328+
ulonglong UNINIT_VAR(value);
13201329
const char *UNINIT_VAR(str);
13211330
size_t UNINIT_VAR(length);
13221331
CHARSET_INFO *UNINIT_VAR(cs);
@@ -1343,14 +1352,17 @@ bool get_interval_value(THD *thd, Item *args,
13431352
}
13441353
else if ((int) int_type <= INTERVAL_MICROSECOND)
13451354
{
1346-
value= args->val_int();
1347-
if (args->null_value)
1348-
return 1;
1349-
if (value < 0)
1350-
{
1351-
interval->neg=1;
1352-
value= -value;
1353-
}
1355+
/*
1356+
Let's use Longlong_hybrid_null to handle correctly:
1357+
- signed and unsigned values
1358+
- the corner case with LONGLONG_MIN
1359+
(avoid undefined behavior with its negation)
1360+
*/
1361+
const Longlong_hybrid_null nr= args->to_longlong_hybrid_null();
1362+
if (nr.is_null())
1363+
return true;
1364+
value= nr.abs();
1365+
interval->neg= nr.neg() ? 1 : 0;
13541366
}
13551367
else
13561368
{
@@ -1377,13 +1389,13 @@ bool get_interval_value(THD *thd, Item *args,
13771389
interval->year= (ulong) value;
13781390
break;
13791391
case INTERVAL_QUARTER:
1380-
interval->month= (ulong)(value*3);
1392+
interval->month= adjust_interval_field_uint32(value, 3);
13811393
break;
13821394
case INTERVAL_MONTH:
13831395
interval->month= (ulong) value;
13841396
break;
13851397
case INTERVAL_WEEK:
1386-
interval->day= (ulong)(value*7);
1398+
interval->day= adjust_interval_field_uint32(value, 7);
13871399
break;
13881400
case INTERVAL_DAY:
13891401
interval->day= (ulong) value;

sql/sql_time.cc

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ void make_truncated_value_warning(THD *thd,
930930
bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
931931
const INTERVAL &interval, bool push_warn)
932932
{
933-
long period, sign;
933+
long sign;
934934

935935
sign= (interval.neg == (bool)ltime->neg ? 1 : -1);
936936

@@ -1001,13 +1001,17 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
10011001
break;
10021002
}
10031003
case INTERVAL_WEEK:
1004-
period= (calc_daynr(ltime->year,ltime->month,ltime->day) +
1005-
sign * (long) interval.day);
1004+
{
1005+
longlong period= calc_daynr(ltime->year, ltime->month, ltime->day) +
1006+
(longlong) sign * (longlong) interval.day;
1007+
if (period < 0 || period > 0x7FFFFFFF)
1008+
goto invalid_date;
10061009
/* Daynumber from year 0 to 9999-12-31 */
10071010
if (get_date_from_daynr((long) period,&ltime->year,&ltime->month,
10081011
&ltime->day))
10091012
goto invalid_date;
10101013
break;
1014+
}
10111015
case INTERVAL_YEAR:
10121016
ltime->year+= sign * (long) interval.year;
10131017
if ((ulong) ltime->year >= 10000L)
@@ -1019,8 +1023,9 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
10191023
case INTERVAL_YEAR_MONTH:
10201024
case INTERVAL_QUARTER:
10211025
case INTERVAL_MONTH:
1022-
period= (ltime->year*12 + sign * (long) interval.year*12 +
1023-
ltime->month-1 + sign * (long) interval.month);
1026+
{
1027+
long period= (ltime->year*12 + sign * (long) interval.year*12 +
1028+
ltime->month-1 + sign * (long) interval.month);
10241029
if ((ulong) period >= 120000L)
10251030
goto invalid_date;
10261031
ltime->year= (uint) (period / 12);
@@ -1033,6 +1038,7 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
10331038
ltime->day++; // Leap-year
10341039
}
10351040
break;
1041+
}
10361042
default:
10371043
goto null_date;
10381044
}

strings/my_strtoll10.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ longlong my_strtoll10(const char *nptr, char **endptr, int *error)
241241
*endptr= (char*) s;
242242
if (negative)
243243
{
244-
if (li >= MAX_NEGATIVE_NUMBER)
244+
if (li >= MAX_NEGATIVE_NUMBER) // Avoid undefined behavior
245245
goto overflow;
246246
return -((longlong) li);
247247
}

0 commit comments

Comments
 (0)