[glib] gdatetime: Fix overflow checks when constructing from timestamps



commit 9374ecc3cb7f45d47fde150c537402ab9ca9d4af
Author: Philip Withnall <withnall endlessm com>
Date:   Tue May 2 23:33:23 2017 +0100

    gdatetime: Fix overflow checks when constructing from timestamps
    
    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 <withnall endlessm com>
    
    https://bugzilla.gnome.org/show_bug.cgi?id=782089

 glib/gdatetime.c       |    9 +++++
 glib/tests/gdatetime.c |   84 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 93 insertions(+), 0 deletions(-)
---
diff --git a/glib/gdatetime.c b/glib/gdatetime.c
index 624d74e..7695adf 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 c54031d..db04870 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);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]