gtimer: Add overflow checks to g_time_val_from_iso8601()

The code was previously doing a few bits of arithmetic without checking
whether they would overflow, and hence not validating the date/time it
was parsing very strictly.

The parser is now not 100% strict, but should avoid any arithmetic
overflows which would cause an invalid result to be returned even though
g_time_val_from_iso8601() had returned TRUE.

oss-fuzz#9673

Signed-off-by: Philip Withnall <withnall@endlessm.com>
This commit is contained in:
Philip Withnall 2018-08-03 14:20:29 +01:00
parent b1fffbffbf
commit cefa66eb76
2 changed files with 84 additions and 18 deletions

View File

@ -357,6 +357,8 @@ g_time_val_from_iso8601 (const gchar *iso_date,
{
struct tm tm = {0};
long val;
long mday, mon, year;
long hour, min, sec;
g_return_val_if_fail (iso_date != NULL, FALSE);
g_return_val_if_fail (time_ != NULL, FALSE);
@ -377,23 +379,35 @@ g_time_val_from_iso8601 (const gchar *iso_date,
if (*iso_date == '-')
{
/* YYYY-MM-DD */
tm.tm_year = val - 1900;
year = val;
iso_date++;
tm.tm_mon = strtoul (iso_date, (char **)&iso_date, 10) - 1;
mon = strtoul (iso_date, (char **)&iso_date, 10);
if (*iso_date++ != '-')
return FALSE;
tm.tm_mday = strtoul (iso_date, (char **)&iso_date, 10);
mday = strtoul (iso_date, (char **)&iso_date, 10);
}
else
{
/* YYYYMMDD */
tm.tm_mday = val % 100;
tm.tm_mon = (val % 10000) / 100 - 1;
tm.tm_year = val / 10000 - 1900;
mday = val % 100;
mon = (val % 10000) / 100;
year = val / 10000;
}
/* Validation. */
if (year < 1900 || year > G_MAXINT)
return FALSE;
if (mon < 1 || mon > 12)
return FALSE;
if (mday < 1 || mday > 31)
return FALSE;
tm.tm_mday = mday;
tm.tm_mon = mon - 1;
tm.tm_year = year - 1900;
if (*iso_date != 'T')
return FALSE;
@ -407,34 +421,50 @@ g_time_val_from_iso8601 (const gchar *iso_date,
if (*iso_date == ':')
{
/* hh:mm:ss */
tm.tm_hour = val;
hour = val;
iso_date++;
tm.tm_min = strtoul (iso_date, (char **)&iso_date, 10);
min = strtoul (iso_date, (char **)&iso_date, 10);
if (*iso_date++ != ':')
return FALSE;
tm.tm_sec = strtoul (iso_date, (char **)&iso_date, 10);
sec = strtoul (iso_date, (char **)&iso_date, 10);
}
else
{
/* hhmmss */
tm.tm_sec = val % 100;
tm.tm_min = (val % 10000) / 100;
tm.tm_hour = val / 10000;
sec = val % 100;
min = (val % 10000) / 100;
hour = val / 10000;
}
/* Validation. Allow up to 2 leap seconds when validating @sec. */
if (hour > 23)
return FALSE;
if (min > 59)
return FALSE;
if (sec > 61)
return FALSE;
tm.tm_hour = hour;
tm.tm_min = min;
tm.tm_sec = sec;
time_->tv_usec = 0;
if (*iso_date == ',' || *iso_date == '.')
{
glong mul = 100000;
while (g_ascii_isdigit (*++iso_date))
while (mul >= 1 && g_ascii_isdigit (*++iso_date))
{
time_->tv_usec += (*iso_date - '0') * mul;
mul /= 10;
}
/* Skip any remaining digits after weve reached our limit of precision. */
while (g_ascii_isdigit (*iso_date))
iso_date++;
}
/* Now parse the offset and convert tm to a time_t */
@ -450,11 +480,24 @@ g_time_val_from_iso8601 (const gchar *iso_date,
val = strtoul (iso_date + 1, (char **)&iso_date, 10);
if (*iso_date == ':')
val = 60 * val + strtoul (iso_date + 1, (char **)&iso_date, 10);
{
/* hh:mm */
hour = val;
min = strtoul (iso_date + 1, (char **)&iso_date, 10);
}
else
val = 60 * (val / 100) + (val % 100);
{
/* hhmm */
hour = val / 100;
min = val % 100;
}
time_->tv_sec = mktime_utc (&tm) + (time_t) (60 * val * sign);
if (hour > 99)
return FALSE;
if (min > 59)
return FALSE;
time_->tv_sec = mktime_utc (&tm) + (time_t) (60 * (60 * hour + min) * sign);
}
else
{

View File

@ -144,7 +144,30 @@ test_timeval_from_iso8601 (void)
{ FALSE, "2001-10-08Tx", { 0, 0 } },
{ FALSE, "2001-10-08T10:11x", { 0, 0 } },
{ FALSE, "Wed Dec 19 17:20:20 GMT 2007", { 0, 0 } },
{ FALSE, "1980-02-22T10:36:00Zulu", { 0, 0 } }
{ FALSE, "1980-02-22T10:36:00Zulu", { 0, 0 } },
{ FALSE, "2T0+819855292164632335", { 0, 0 } },
{ TRUE, "2018-08-03T14:08:05.446178377+01:00", { 1533301685, 446178 } },
{ FALSE, "2147483648-08-03T14:08:05.446178377+01:00", { 0, 0 } },
{ FALSE, "2018-13-03T14:08:05.446178377+01:00", { 0, 0 } },
{ FALSE, "2018-00-03T14:08:05.446178377+01:00", { 0, 0 } },
{ FALSE, "2018-08-00T14:08:05.446178377+01:00", { 0, 0 } },
{ FALSE, "2018-08-32T14:08:05.446178377+01:00", { 0, 0 } },
{ FALSE, "2018-08-03T24:08:05.446178377+01:00", { 0, 0 } },
{ FALSE, "2018-08-03T14:60:05.446178377+01:00", { 0, 0 } },
{ FALSE, "2018-08-03T14:08:63.446178377+01:00", { 0, 0 } },
{ FALSE, "2018-08-03T14:08:05.446178377+100:00", { 0, 0 } },
{ FALSE, "2018-08-03T14:08:05.446178377+01:60", { 0, 0 } },
{ TRUE, "20180803T140805.446178377+0100", { 1533301685, 446178 } },
{ FALSE, "21474836480803T140805.446178377+0100", { 0, 0 } },
{ FALSE, "20181303T140805.446178377+0100", { 0, 0 } },
{ FALSE, "20180003T140805.446178377+0100", { 0, 0 } },
{ FALSE, "20180800T140805.446178377+0100", { 0, 0 } },
{ FALSE, "20180832T140805.446178377+0100", { 0, 0 } },
{ FALSE, "20180803T240805.446178377+0100", { 0, 0 } },
{ FALSE, "20180803T146005.446178377+0100", { 0, 0 } },
{ FALSE, "20180803T140863.446178377+0100", { 0, 0 } },
{ FALSE, "20180803T140805.446178377+10000", { 0, 0 } },
{ FALSE, "20180803T140805.446178377+0160", { 0, 0 } },
};
GTimeVal out;
gboolean success;