Bug #121513 TIME string parser takes the hour, minute and second fields modulo 2^64: CAST('18446744073709551635:40:27' AS TIME) = 19
Submitted: 15 Oct 5:12
Reporter: Ke Han Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: Data Types Severity:S3 (Non-critical)
Version:26.10.0 (trunk, 3b99be40), 26.7.0, 8.4.1 OS:Any
Assigned to: CPU Architecture:Any

[15 Oct 5:12] Ke Han
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().