Description:
A TIME string whose hour, minute or second field is 2^64 + k is read as if the
field were k. The result is a valid time and there is no warning. A field of
2^32 + k (much smaller) is correctly rejected:
SELECT CAST('18446744073709551635:40:27' AS TIME); -- 19:40:27 hour 2^64+19
SELECT CAST('18446744073709551616:40:27' AS TIME); -- 00:40:27 hour 2^64
SELECT CAST('4294967296:40:27' AS TIME); -- NULL + 1292 Truncated incorrect time value
SELECT CAST('19:18446744073709551656:27' AS TIME); -- 19:40:27 minute 2^64+40
SELECT CAST('19:40:18446744073709551643' AS TIME); -- 19:40:27 second 2^64+27
SELECT CAST('19:40:4294967323' AS TIME); -- NULL + 1292
SELECT CAST('19:40:60' AS TIME); -- NULL + 1292
SELECT TIME_TO_SEC('-838:59:18446744073709551675'); -- -3020399 (= -838:59:59)
SELECT TIMEDIFF('18446744073709551635:00:00', '00:00:00'); -- 19:00:00
The wrapped value is stored, also under strict mode, where '19:40:60' is rejected:
CREATE TABLE t22 (t TIME);
SET sql_mode = 'STRICT_ALL_TABLES';
INSERT INTO t22 VALUES ('18446744073709551635:40:27'), ('19:40:18446744073709551643');
-- succeeds; both rows are 19:40:27
INSERT INTO t22 VALUES ('19:40:60'); -- ERROR 1292 Incorrect time value
MariaDB rejects the hour case; TiDB rejects all of them.
Where (trunk 3b99be40): mysys/my_time.cc, str_to_time(). Each field is
accumulated in an unsigned 64-bit value with no overflow check:
802 for (value = 0; str != end && isdigit_char(*str); str++)
803 value = value * 10L + static_cast<long>(*str - '0');
805 if (value > UINT_MAX) return true;
...
843 for (value = 0; str != end && isdigit_char(*str); str++)
844 value = value * 10L + static_cast<long>(*str - '0');
845 date[state++] = static_cast<ulong>(value);
For the first field the only test, value > UINT_MAX, comes after the wrap; the
later fields have no test at all before the 0..59 range check, which sees the
wrapped value.
How to repeat:
SELECT CAST('18446744073709551635:40:27' AS TIME);
SELECT CAST('19:18446744073709551656:27' AS TIME);
SELECT CAST('19:40:18446744073709551643' AS TIME);
SELECT CAST('19:40:4294967323' AS TIME);
-- expected: NULL, NULL, NULL, NULL (each with warning 1292)
-- actual: 19:40:27, 19:40:27, 19:40:27, NULL (only the last warns)
Suggested fix:
Stop accumulating once value exceeds a bound larger than any valid field (for
example UINT_MAX, which the first field is already compared with, or
ULLONG_MAX / 10 before the multiplication) and return an error, in both digit
loops of str_to_time().
Description: A TIME string whose hour, minute or second field is 2^64 + k is read as if the field were k. The result is a valid time and there is no warning. A field of 2^32 + k (much smaller) is correctly rejected: SELECT CAST('18446744073709551635:40:27' AS TIME); -- 19:40:27 hour 2^64+19 SELECT CAST('18446744073709551616:40:27' AS TIME); -- 00:40:27 hour 2^64 SELECT CAST('4294967296:40:27' AS TIME); -- NULL + 1292 Truncated incorrect time value SELECT CAST('19:18446744073709551656:27' AS TIME); -- 19:40:27 minute 2^64+40 SELECT CAST('19:40:18446744073709551643' AS TIME); -- 19:40:27 second 2^64+27 SELECT CAST('19:40:4294967323' AS TIME); -- NULL + 1292 SELECT CAST('19:40:60' AS TIME); -- NULL + 1292 SELECT TIME_TO_SEC('-838:59:18446744073709551675'); -- -3020399 (= -838:59:59) SELECT TIMEDIFF('18446744073709551635:00:00', '00:00:00'); -- 19:00:00 The wrapped value is stored, also under strict mode, where '19:40:60' is rejected: CREATE TABLE t22 (t TIME); SET sql_mode = 'STRICT_ALL_TABLES'; INSERT INTO t22 VALUES ('18446744073709551635:40:27'), ('19:40:18446744073709551643'); -- succeeds; both rows are 19:40:27 INSERT INTO t22 VALUES ('19:40:60'); -- ERROR 1292 Incorrect time value MariaDB rejects the hour case; TiDB rejects all of them. Where (trunk 3b99be40): mysys/my_time.cc, str_to_time(). Each field is accumulated in an unsigned 64-bit value with no overflow check: 802 for (value = 0; str != end && isdigit_char(*str); str++) 803 value = value * 10L + static_cast<long>(*str - '0'); 805 if (value > UINT_MAX) return true; ... 843 for (value = 0; str != end && isdigit_char(*str); str++) 844 value = value * 10L + static_cast<long>(*str - '0'); 845 date[state++] = static_cast<ulong>(value); For the first field the only test, value > UINT_MAX, comes after the wrap; the later fields have no test at all before the 0..59 range check, which sees the wrapped value. How to repeat: SELECT CAST('18446744073709551635:40:27' AS TIME); SELECT CAST('19:18446744073709551656:27' AS TIME); SELECT CAST('19:40:18446744073709551643' AS TIME); SELECT CAST('19:40:4294967323' AS TIME); -- expected: NULL, NULL, NULL, NULL (each with warning 1292) -- actual: 19:40:27, 19:40:27, 19:40:27, NULL (only the last warns) Suggested fix: Stop accumulating once value exceeds a bound larger than any valid field (for example UINT_MAX, which the first field is already compared with, or ULLONG_MAX / 10 before the multiplication) and return an error, in both digit loops of str_to_time().