bitwiseworks / libc

LIBC Next (kLIBC fork)
9 stars 4 forks source link

Year 2060 problem #110

Open dmik opened 3 years ago

dmik commented 3 years ago

As shown in https://github.com/bitwiseworks/libc/issues/109#issuecomment-882764025, kLIBC supports years from 1900 till 2059 when handling time in localtime, gmtime, mktime and friends, see https://github.com/bitwiseworks/libc/blob/2437044df1ec3ed3a1b737e9127ca68a55f8b6ab/src/emx/include/emx/time.h#L13

The EMX-specific _YEARS constant defines the size of various internal structures used to track DST start / stop moments. These structures are also used to convert time between Unix and OS/2 DATETIME.

The main internal structure is _year_day which is the number of days since 1970-01-01 till January, 1st of a year in question. It uses short integer (i.e. with a range of days from -32768 to 32767). This is why year 2060 is top of this list.

Having a real date with year 2060 or later (or feeding functions with years below 1900 or above 2060) will cause the gmtime function (also sed by localtime) to just return NULL instead of a valid tm struct. For the case of __settime (used to convert unix time to DATETIME) I see an infinite loop of the binary search cycle resulting into an application hang or a crash due to arbitrary memory access. Also, strptime seems to be affected which doesn't check for year's bounds and assumes _year_day contains all years of the infinity (causing arbitrary memory access for years less than 1900 or greater than 2059.

We need to deal with that at some point if we want LIBC to handle dates after year 2059.

Ideas are welcome.

dmik commented 3 years ago

Here is a simple test case:

#include <stdio.h>
#include <time.h>
#include <string.h>

void test_time(const char *msg, const time64_t t)
{
  struct tm tm;

  memset(&tm, 0, sizeof(tm));
  if (_gmtime64_r(&t, &tm))
    printf ("%s: _gmtime64_r: year %d, mon %d, mday %d, hour %d, min %d, sec %d, isdst %d\n", msg, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_isdst);
  else
    printf ("%s: _gmtime64_r: failed\n", msg);

  memset(&tm, 0, sizeof(tm));
  if (_localtime64_r(&t, &tm))
    printf ("%s: _localtime64_r: year %d, mon %d, mday %d, hour %d, min %d, sec %d, isdst %d\n", msg, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_isdst);
  else
    printf ("%s: _localtime64_r: failed\n", msg);
}

void test_time_str(const char *msg, const char *str, const char *fmt)
{
  struct tm tm;
  memset(&tm, 0, sizeof(tm));
  if (strptime(str, fmt, &tm)) 
    printf ("%s: strptime: year %d, mon %d, mday %d, hour %d, min %d, sec %d, isdst %d yday %d\n", msg, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_isdst, tm.tm_yday);
  else
    printf ("%s: strptime: failed\n", msg);
}

int main()
{
  const time64_t y2038 = 2158963200LL; // midnight, 01-Jun-2038 GMT
  const time64_t y2060 = 2853273600LL; // midnight, 01-Jun-2060 GMT

  test_time("y2038", y2038);
  test_time("y2060", y2060);

  const char *fmt1 = "%Y-%m-%d %H:%M:%S";
  const char *y2038_str1 = "2038-06-01 00:00:00";
  const char *y2060_str1 = "2060-06-01 00:00:00";

  test_time_str("y2038_str1", y2038_str1, fmt1);
  test_time_str("y2060_str1", y2060_str1, fmt1);

  const char *fmt2 = "%Y %j";
  const char *y2038_str2 = "2038 1";
  const char *y2060_str2 = "2070 1";

  test_time_str("y2038_str2", y2038_str2, fmt2);
  test_time_str("y2060_str2", y2060_str2, fmt2);

  return 0;
}

I could trigger the gmtime failure and the localtime crash (because it doesn't check for gmtime returning NULL) for year 2060 and further. However, I couldn't trigger any problems with strptime so far (more testing needed).

dmik commented 3 years ago

I'm postponing this until we decide what's the best route here.