From 4ddabfc61214348d365202a488091a3236b40e97 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 30 Jul 2019 14:37:48 +0100 Subject: [PATCH 1/2] gdatetime: Avoid an assertion failure when parsing some ISO 8601 dates Some malformed ISO 8601 date/time strings were causing an assertion failure when passed to `g_date_time_new_from_iso8601()`, due to a mismatch between the bounds checking of timezone offsets in `GDateTime` and `GTimeZone`. Fix that and add a unit test for it. oss-fuzz#16101 Signed-off-by: Philip Withnall --- glib/gdatetime.c | 14 ++++++++------ glib/tests/gdatetime.c | 3 +++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/glib/gdatetime.c b/glib/gdatetime.c index 58874ad2a..b364e18b3 100644 --- a/glib/gdatetime.c +++ b/glib/gdatetime.c @@ -1327,9 +1327,7 @@ static GTimeZone * parse_iso8601_timezone (const gchar *text, gsize length, gssize *tz_offset) { gint i, tz_length, offset_hours, offset_minutes; -#ifndef G_DISABLE_ASSERT gint offset_sign = 1; -#endif GTimeZone *tz; /* UTC uses Z suffix */ @@ -1343,9 +1341,7 @@ parse_iso8601_timezone (const gchar *text, gsize length, gssize *tz_offset) for (i = length - 1; i >= 0; i--) if (text[i] == '+' || text[i] == '-') { -#ifndef G_DISABLE_ASSERT offset_sign = text[i] == '-' ? -1 : 1; -#endif break; } if (i < 0) @@ -1380,8 +1376,14 @@ parse_iso8601_timezone (const gchar *text, gsize length, gssize *tz_offset) tz = g_time_zone_new (text + i); /* Double-check that the GTimeZone matches our interpretation of the timezone. - * Failure would indicate a bug either here of in the GTimeZone code. */ - g_assert (g_time_zone_get_offset (tz, 0) == offset_sign * (offset_hours * 3600 + offset_minutes * 60)); + * This can fail because our interpretation is less strict than (for example) + * parse_time() in gtimezone.c, which restricts the range of the parsed + * integers. */ + if (g_time_zone_get_offset (tz, 0) != offset_sign * (offset_hours * 3600 + offset_minutes * 60)) + { + g_time_zone_unref (tz); + return NULL; + } return tz; } diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c index 9afcf3926..2eb8d462e 100644 --- a/glib/tests/gdatetime.c +++ b/glib/tests/gdatetime.c @@ -499,6 +499,9 @@ test_GDateTime_new_from_iso8601 (void) dt = g_date_time_new_from_iso8601 ("not a date", NULL); g_assert_null (dt); + dt = g_date_time_new_from_iso8601 (" +55", NULL); + g_assert_null (dt); + /* Check common case */ dt = g_date_time_new_from_iso8601 ("2016-08-24T22:10:42Z", NULL); ASSERT_DATE (dt, 2016, 8, 24); From 9564c6541b76db7623348367240f32f5feabb2c6 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 30 Jul 2019 14:39:19 +0100 Subject: [PATCH 2/2] tests: Add ISO 8601 parsing tests for g_date_time_new_from_iso8601() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are copies of the existing tests for `g_time_val_from_iso8601()`, with the test strings which fail for `GDateTime` commented out. This is OK, as it’s documented as only accepting a subset of ISO 8601 (and for some of the test vectors, it’s debatable whether they’re actually valid ISO 8601, depending on how you interpret the valid bounds of timezone offsets — some interpretations of the available documentation would say that timezone offsets should never be ≥24 hours or ≥60 minutes). There is one test string which is not accepted by `g_time_val_from_iso8601()` but which is accepted by `GDateTime`, as `g_date_time_new_from_iso8601()` actually accepts RFC 3339, which is a little more liberal than ISO 8601. Fun times. See https://tools.ietf.org/html/rfc3339#section-5.6. Signed-off-by: Philip Withnall --- glib/tests/gdatetime.c | 107 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c index 2eb8d462e..23d83b7d0 100644 --- a/glib/tests/gdatetime.c +++ b/glib/tests/gdatetime.c @@ -793,6 +793,112 @@ test_GDateTime_new_from_iso8601 (void) g_assert_null (dt); } +typedef struct { + gboolean success; + const gchar *in; + + /* Expected result: */ + guint year; + guint month; + guint day; + guint hour; + guint minute; + guint second; + guint microsecond; + GTimeSpan utc_offset; +} Iso8601ParseTest; + +static void +test_GDateTime_new_from_iso8601_2 (void) +{ + const Iso8601ParseTest tests[] = { + { TRUE, "1990-11-01T10:21:17Z", 1990, 11, 1, 10, 21, 17, 0, 0 }, + { TRUE, "19901101T102117Z", 1990, 11, 1, 10, 21, 17, 0, 0 }, + { TRUE, "1970-01-01T00:00:17.12Z", 1970, 1, 1, 0, 0, 17, 120000, 0 }, + { TRUE, "1970-01-01T00:00:17.1234Z", 1970, 1, 1, 0, 0, 17, 123400, 0 }, + { TRUE, "1970-01-01T00:00:17.123456Z", 1970, 1, 1, 0, 0, 17, 123456, 0 }, + { TRUE, "1980-02-22T12:36:00+02:00", 1980, 2, 22, 12, 36, 0, 0, 2 * G_TIME_SPAN_HOUR }, + { FALSE, " ", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "x", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "123x", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2001-10+x", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "1980-02-22T", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2001-10-08Tx", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2001-10-08T10:11x", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "Wed Dec 19 17:20:20 GMT 2007", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "1980-02-22T10:36:00Zulu", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2T0+819855292164632335", 0, 0, 0, 0, 0, 0, 0, 0 }, + { TRUE, "2018-08-03T14:08:05.446178377+01:00", 2018, 8, 3, 14, 8, 5, 446178, 1 * G_TIME_SPAN_HOUR }, + { FALSE, "2147483648-08-03T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2018-13-03T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2018-00-03T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2018-08-00T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2018-08-32T14:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2018-08-03T24:08:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2018-08-03T14:60:05.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2018-08-03T14:08:63.446178377+01:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2018-08-03T14:08:05.446178377+100:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { TRUE, "20180803T140805.446178377+0100", 2018, 8, 3, 14, 8, 5, 446178, 1 * G_TIME_SPAN_HOUR }, + { FALSE, "21474836480803T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "20181303T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "20180003T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "20180800T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "20180832T140805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "20180803T240805.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "20180803T146005.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "20180803T140863.446178377+0100", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "20180803T140805.446178377+10000", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "-0005-01-01T00:00:00Z", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "2018-08-06", 0, 0, 0, 0, 0, 0, 0, 0 }, + /* This is not accepted by g_time_val_from_iso8601(), but is accepted by g_date_time_new_from_iso8601(): + { FALSE, "2018-08-06 13:51:00Z", 0, 0, 0, 0, 0, 0, 0, 0 }, + * */ + { TRUE, "20180803T140805,446178377+0100", 2018, 8, 3, 14, 8, 5, 446178, 1 * G_TIME_SPAN_HOUR }, + { TRUE, "2018-08-03T14:08:05.446178377-01:00", 2018, 8, 3, 14, 8, 5, 446178, -1 * G_TIME_SPAN_HOUR }, + { FALSE, "2018-08-03T14:08:05.446178377 01:00", 0, 0, 0, 0, 0, 0, 0, 0 }, + { TRUE, "1990-11-01T10:21:17", 1990, 11, 1, 10, 21, 17, 0, 0 }, + /* These are accepted by g_time_val_from_iso8601(), but not by g_date_time_new_from_iso8601(): + { TRUE, "19901101T102117+5", 1990, 11, 1, 10, 21, 17, 0, 5 * G_TIME_SPAN_HOUR }, + { TRUE, "19901101T102117+3:15", 1990, 11, 1, 10, 21, 17, 0, 3 * G_TIME_SPAN_HOUR + 15 * G_TIME_SPAN_MINUTE }, + { TRUE, " 1990-11-01T10:21:17Z ", 1990, 11, 1, 10, 21, 17, 0, 0 }, + { FALSE, "2018-08-03T14:08:05.446178377+01:60", 0, 0, 0, 0, 0, 0, 0, 0 }, + { FALSE, "20180803T140805.446178377+0160", 0, 0, 0, 0, 0, 0, 0, 0 }, + { TRUE, "+1980-02-22T12:36:00+02:00", 1980, 2, 22, 12, 36, 0, 0, 2 * G_TIME_SPAN_HOUR }, + { TRUE, "1990-11-01T10:21:17 ", 1990, 11, 1, 10, 21, 17, 0, 0 }, + */ + }; + GTimeZone *tz = NULL; + GDateTime *dt = NULL; + gsize i; + + g_test_summary ("Further parser tests for g_date_time_new_from_iso8601(), " + "checking success and failure using test vectors."); + + tz = g_time_zone_new_utc (); + + for (i = 0; i < G_N_ELEMENTS (tests); i++) + { + g_test_message ("Vector %" G_GSIZE_FORMAT ": %s", i, tests[i].in); + + dt = g_date_time_new_from_iso8601 (tests[i].in, tz); + if (tests[i].success) + { + g_assert_nonnull (dt); + ASSERT_DATE (dt, tests[i].year, tests[i].month, tests[i].day); + ASSERT_TIME (dt, tests[i].hour, tests[i].minute, tests[i].second, tests[i].microsecond); + g_assert_cmpint (g_date_time_get_utc_offset (dt), ==, tests[i].utc_offset); + } + else + { + g_assert_null (dt); + } + + g_clear_pointer (&dt, g_date_time_unref); + } + + g_time_zone_unref (tz); +} + static void test_GDateTime_to_unix (void) { @@ -2584,6 +2690,7 @@ main (gint argc, g_test_add_func ("/GDateTime/new_from_timeval_utc", test_GDateTime_new_from_timeval_utc); g_test_add_func ("/GDateTime/new_from_timeval/overflow", test_GDateTime_new_from_timeval_overflow); g_test_add_func ("/GDateTime/new_from_iso8601", test_GDateTime_new_from_iso8601); + g_test_add_func ("/GDateTime/new_from_iso8601/2", test_GDateTime_new_from_iso8601_2); g_test_add_func ("/GDateTime/new_full", test_GDateTime_new_full); g_test_add_func ("/GDateTime/now", test_GDateTime_now); g_test_add_func ("/GDateTime/printf", test_GDateTime_printf);