From 9374ecc3cb7f45d47fde150c537402ab9ca9d4af Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 2 May 2017 23:33:23 +0100 Subject: [PATCH] gdatetime: Fix overflow checks when constructing from timestamps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GDateTime does overflow checks to see if the timestamp being passed in is too big to be represented. However, it only does those after converting from a timestamp to an interval, which involves some multiplications and additions — and hence can overflow, and cause the later bounds check to erroneously succeed. This results in a non-NULL GDateTime being returned which represents completely the wrong date. Fix the overflow checks (do them earlier) and add some unit tests. Signed-off-by: Philip Withnall https://bugzilla.gnome.org/show_bug.cgi?id=782089 --- glib/gdatetime.c | 9 +++++ glib/tests/gdatetime.c | 84 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/glib/gdatetime.c b/glib/gdatetime.c index 624d74e2b..7695adf09 100644 --- a/glib/gdatetime.c +++ b/glib/gdatetime.c @@ -129,6 +129,8 @@ struct _GDateTime ((instant)/USEC_PER_SECOND - UNIX_EPOCH_START * SEC_PER_DAY) #define UNIX_TO_INSTANT(unix) \ (((unix) + UNIX_EPOCH_START * SEC_PER_DAY) * USEC_PER_SECOND) +#define UNIX_TO_INSTANT_IS_VALID(unix) \ + ((gint64) (unix) <= INSTANT_TO_UNIX (G_MAXINT64)) #define DAYS_IN_4YEARS 1461 /* days in 4 years */ #define DAYS_IN_100YEARS 36524 /* days in 100 years */ @@ -650,6 +652,10 @@ static GDateTime * g_date_time_new_from_timeval (GTimeZone *tz, const GTimeVal *tv) { + if ((gint64) tv->tv_sec > G_MAXINT64 - 1 || + !UNIX_TO_INSTANT_IS_VALID ((gint64) tv->tv_sec + 1)) + return NULL; + return g_date_time_from_instant (tz, tv->tv_usec + UNIX_TO_INSTANT (tv->tv_sec)); } @@ -679,6 +685,9 @@ static GDateTime * g_date_time_new_from_unix (GTimeZone *tz, gint64 secs) { + if (!UNIX_TO_INSTANT_IS_VALID (secs)) + return NULL; + return g_date_time_from_instant (tz, UNIX_TO_INSTANT (secs)); } diff --git a/glib/tests/gdatetime.c b/glib/tests/gdatetime.c index c54031db8..db04870d2 100644 --- a/glib/tests/gdatetime.c +++ b/glib/tests/gdatetime.c @@ -139,6 +139,23 @@ test_GDateTime_new_from_unix (void) g_date_time_unref (dt); } +/* Check that trying to create a #GDateTime too far in the future reliably + * fails. Previously, the checks for this overflowed and it silently returned + * an incorrect #GDateTime. */ +static void +test_GDateTime_new_from_unix_overflow (void) +{ + GDateTime *dt; + + g_test_bug ("782089"); + + dt = g_date_time_new_from_unix_utc (G_MAXINT64); + g_assert_null (dt); + + dt = g_date_time_new_from_unix_local (G_MAXINT64); + g_assert_null (dt); +} + static void test_GDateTime_invalid (void) { @@ -353,6 +370,71 @@ test_GDateTime_new_from_timeval (void) g_date_time_unref (dt); } +static gint64 +find_maximum_supported_tv_sec (void) +{ + glong highest_success = 0, lowest_failure = G_MAXLONG; + GTimeVal tv; + + tv.tv_usec = 0; + + while (highest_success < lowest_failure - 1) + { + GDateTime *dt; + + tv.tv_sec = (highest_success + lowest_failure) / 2; + dt = g_date_time_new_from_timeval_utc (&tv); + + if (dt != NULL) + { + highest_success = tv.tv_sec; + g_date_time_unref (dt); + } + else + { + lowest_failure = tv.tv_sec; + } + } + + return highest_success; +} + +/* Check that trying to create a #GDateTime too far in the future reliably + * fails. With a #GTimeVal, this is subtle, as the tv_usec are added into the + * calculation part-way through. */ +static void +test_GDateTime_new_from_timeval_overflow (void) +{ + GDateTime *dt; + GTimeVal tv; + + g_test_bug ("782089"); + + tv.tv_sec = G_MAXLONG; + tv.tv_usec = 0; + + dt = g_date_time_new_from_timeval_utc (&tv); + g_assert_null (dt); + + dt = g_date_time_new_from_timeval_local (&tv); + g_assert_null (dt); + + tv.tv_sec = find_maximum_supported_tv_sec (); + tv.tv_usec = G_USEC_PER_SEC - 1; + + g_test_message ("Maximum supported GTimeVal.tv_sec = %lu", tv.tv_sec); + + dt = g_date_time_new_from_timeval_utc (&tv); + g_assert_nonnull (dt); + g_date_time_unref (dt); + + tv.tv_sec++; + tv.tv_usec = 0; + + dt = g_date_time_new_from_timeval_utc (&tv); + g_assert_null (dt); +} + static void test_GDateTime_new_from_timeval_utc (void) { @@ -1652,8 +1734,10 @@ main (gint argc, g_test_add_func ("/GDateTime/hash", test_GDateTime_hash); g_test_add_func ("/GDateTime/new_from_unix", test_GDateTime_new_from_unix); g_test_add_func ("/GDateTime/new_from_unix_utc", test_GDateTime_new_from_unix_utc); + g_test_add_func ("/GDateTime/new_from_unix/overflow", test_GDateTime_new_from_unix_overflow); g_test_add_func ("/GDateTime/new_from_timeval", test_GDateTime_new_from_timeval); 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_full", test_GDateTime_new_full); g_test_add_func ("/GDateTime/now", test_GDateTime_now); g_test_add_func ("/GDateTime/printf", test_GDateTime_printf);