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.
This commit is contained in:
Alexander Barkov 2024-05-23 16:42:15 +04:00
parent 44b23bb184
commit 7925326183
5 changed files with 83 additions and 17 deletions

View file

@ -200,3 +200,34 @@ select 30 + (20010101 + interval 2 day), x from v1;
20010133 20010133
drop view v1;
End of 10.2 tests
#
# Start of 10.5 tests
#
#
# 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
#
SELECT DATE_ADD('01-01-23',INTERVAL '9223372036854775808-02' WEEK);
DATE_ADD('01-01-23',INTERVAL '9223372036854775808-02' WEEK)
NULL
Warnings:
Warning 1292 Truncated incorrect INTEGER value: '9223372036854775808-02'
Warning 1441 Datetime function: datetime field overflow
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775807 WEEK);
DATE_ADD('01-01-23',INTERVAL -9223372036854775807 WEEK)
NULL
Warnings:
Warning 1441 Datetime function: datetime field overflow
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775808 WEEK);
DATE_ADD('01-01-23',INTERVAL -9223372036854775808 WEEK)
NULL
Warnings:
Warning 1441 Datetime function: datetime field overflow
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775809 WEEK);
DATE_ADD('01-01-23',INTERVAL -9223372036854775809 WEEK)
NULL
Warnings:
Warning 1916 Got overflow when converting '-9223372036854775809' to INT. Value truncated
Warning 1441 Datetime function: datetime field overflow
#
# End of 10.5 tests
#

View file

@ -169,3 +169,20 @@ select 30 + (20010101 + interval 2 day), x from v1;
drop view v1;
--echo End of 10.2 tests
--echo #
--echo # Start of 10.5 tests
--echo #
--echo #
--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
--echo #
SELECT DATE_ADD('01-01-23',INTERVAL '9223372036854775808-02' WEEK);
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775807 WEEK);
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775808 WEEK);
SELECT DATE_ADD('01-01-23',INTERVAL -9223372036854775809 WEEK);
--echo #
--echo # End of 10.5 tests
--echo #

View file

@ -1306,6 +1306,15 @@ my_decimal *Item_func_time_to_sec::decimal_op(my_decimal* buf)
}
static inline
uint32 adjust_interval_field_uint32(ulonglong value, int32 multiplier)
{
return value > ((ulonglong) (uint32) (UINT_MAX32)) / multiplier ?
(uint32) UINT_MAX32 :
(uint32) (value * multiplier);
}
/**
Convert a string to a interval value.
@ -1316,7 +1325,7 @@ bool get_interval_value(THD *thd, Item *args,
interval_type int_type, INTERVAL *interval)
{
ulonglong array[5];
longlong UNINIT_VAR(value);
ulonglong UNINIT_VAR(value);
const char *UNINIT_VAR(str);
size_t UNINIT_VAR(length);
CHARSET_INFO *UNINIT_VAR(cs);
@ -1343,14 +1352,17 @@ bool get_interval_value(THD *thd, Item *args,
}
else if ((int) int_type <= INTERVAL_MICROSECOND)
{
value= args->val_int();
if (args->null_value)
return 1;
if (value < 0)
{
interval->neg=1;
value= -value;
}
/*
Let's use Longlong_hybrid_null to handle correctly:
- signed and unsigned values
- the corner case with LONGLONG_MIN
(avoid undefined behavior with its negation)
*/
const Longlong_hybrid_null nr= args->to_longlong_hybrid_null();
if (nr.is_null())
return true;
value= nr.abs();
interval->neg= nr.neg() ? 1 : 0;
}
else
{
@ -1377,13 +1389,13 @@ bool get_interval_value(THD *thd, Item *args,
interval->year= (ulong) value;
break;
case INTERVAL_QUARTER:
interval->month= (ulong)(value*3);
interval->month= adjust_interval_field_uint32(value, 3);
break;
case INTERVAL_MONTH:
interval->month= (ulong) value;
break;
case INTERVAL_WEEK:
interval->day= (ulong)(value*7);
interval->day= adjust_interval_field_uint32(value, 7);
break;
case INTERVAL_DAY:
interval->day= (ulong) value;

View file

@ -930,7 +930,7 @@ void make_truncated_value_warning(THD *thd,
bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
const INTERVAL &interval, bool push_warn)
{
long period, sign;
long sign;
sign= (interval.neg == (bool)ltime->neg ? 1 : -1);
@ -1001,13 +1001,17 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
break;
}
case INTERVAL_WEEK:
period= (calc_daynr(ltime->year,ltime->month,ltime->day) +
sign * (long) interval.day);
{
longlong period= calc_daynr(ltime->year, ltime->month, ltime->day) +
(longlong) sign * (longlong) interval.day;
if (period < 0 || period > 0x7FFFFFFF)
goto invalid_date;
/* Daynumber from year 0 to 9999-12-31 */
if (get_date_from_daynr((long) period,&ltime->year,&ltime->month,
&ltime->day))
goto invalid_date;
break;
}
case INTERVAL_YEAR:
ltime->year+= sign * (long) interval.year;
if ((ulong) ltime->year >= 10000L)
@ -1019,8 +1023,9 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
case INTERVAL_YEAR_MONTH:
case INTERVAL_QUARTER:
case INTERVAL_MONTH:
period= (ltime->year*12 + sign * (long) interval.year*12 +
ltime->month-1 + sign * (long) interval.month);
{
long period= (ltime->year*12 + sign * (long) interval.year*12 +
ltime->month-1 + sign * (long) interval.month);
if ((ulong) period >= 120000L)
goto invalid_date;
ltime->year= (uint) (period / 12);
@ -1033,6 +1038,7 @@ bool date_add_interval(THD *thd, MYSQL_TIME *ltime, interval_type int_type,
ltime->day++; // Leap-year
}
break;
}
default:
goto null_date;
}

View file

@ -241,7 +241,7 @@ end4:
*endptr= (char*) s;
if (negative)
{
if (li >= MAX_NEGATIVE_NUMBER)
if (li >= MAX_NEGATIVE_NUMBER) // Avoid undefined behavior
goto overflow;
return -((longlong) li);
}