Avoid conversion overflow from struct tm.

See discussion in
https://boringssl-review.googlesource.com/c/boringssl/+/65967/comment/4b0fb2a6_78bfab3a/

struct tm is defined with tm_ values as all ints.

Conversion inside this code is all bounds checked around valid
times and can't overflow, but because struct tm uses 1 based months
and 1900 based years, we need to modify the input values when
converting a struct tm.

Rather than do awkward bounds checks, just accept an int64_t on
input and we don't have to care.

OPENSSL_gmtime_adj gains checks to ensure the cumulative days
and seconds values passed in may not overflow.

While we are here we also ensure that OPENSSL_gmtime_adj does
not modify the output struct tm in the failure case.

Also while we are here, just make the offset_seconds value of
OPENSSL_gmtime_adj an int64_t - because long was never the
correct type.

Add tests for all this.

Change-Id: I40ac019c4274b5388c97736cf85cede951d8b7ae
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/66047
Commit-Queue: Bob Beck <bbe@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
Auto-Submit: Bob Beck <bbe@google.com>
diff --git a/crypto/asn1/asn1_test.cc b/crypto/asn1/asn1_test.cc
index 76d6889..314ac3d 100644
--- a/crypto/asn1/asn1_test.cc
+++ b/crypto/asn1/asn1_test.cc
@@ -1162,8 +1162,8 @@
   struct tm tm1, tm2;
   int days, secs;
 
-  OPENSSL_posix_to_tm(0, &tm1);
-  OPENSSL_posix_to_tm(0, &tm2);
+  EXPECT_TRUE(OPENSSL_posix_to_tm(0, &tm1));
+  EXPECT_TRUE(OPENSSL_posix_to_tm(0, &tm2));
   // Test values that are too large and should be rejected.
   EXPECT_FALSE(OPENSSL_gmtime_adj(&tm1, INT_MIN, INT_MIN));
   EXPECT_FALSE(OPENSSL_gmtime_adj(&tm1, INT_MAX, INT_MAX));
@@ -2475,6 +2475,87 @@
 #endif
 }
 
+static auto TimeToTuple(const tm &t) {
+  return std::make_tuple(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min,
+                         t.tm_sec);
+}
+
+TEST(ASN1Test, TimeOverflow) {
+  // Input time is out of range and may overflow internal calculations to shift
+  // |tm_year| and |tm_mon| to a more normal value.
+  tm overflow_year = {};
+  overflow_year.tm_year = INT_MAX - 1899;
+  overflow_year.tm_mday = 1;
+  tm overflow_month = {};
+  overflow_month.tm_mon = INT_MAX;
+  overflow_month.tm_mday = 1;
+  int64_t posix_u64;
+  EXPECT_FALSE(OPENSSL_tm_to_posix(&overflow_year, &posix_u64));
+  EXPECT_FALSE(OPENSSL_tm_to_posix(&overflow_month, &posix_u64));
+  time_t posix;
+  EXPECT_FALSE(OPENSSL_timegm(&overflow_year, &posix));
+  EXPECT_FALSE(OPENSSL_timegm(&overflow_month, &posix));
+  EXPECT_FALSE(
+      OPENSSL_gmtime_adj(&overflow_year, /*offset_day=*/0, /*offset_sec=*/0));
+  EXPECT_FALSE(
+      OPENSSL_gmtime_adj(&overflow_month, /*offset_day=*/0, /*offset_sec=*/0));
+  int days, secs;
+  EXPECT_FALSE(
+      OPENSSL_gmtime_diff(&days, &secs, &overflow_year, &overflow_year));
+  EXPECT_FALSE(
+      OPENSSL_gmtime_diff(&days, &secs, &overflow_month, &overflow_month));
+
+  // Input time is in range, but even adding one second puts it out of range.
+  tm max_time = {};
+  max_time.tm_year = 9999 - 1900;
+  max_time.tm_mon = 12 - 1;
+  max_time.tm_mday = 31;
+  max_time.tm_hour = 23;
+  max_time.tm_min = 59;
+  max_time.tm_sec = 59;
+  tm copy = max_time;
+  EXPECT_TRUE(OPENSSL_gmtime_adj(&copy, /*offset_day=*/0, /*offset_sec=*/0));
+  EXPECT_EQ(TimeToTuple(copy), TimeToTuple(max_time));
+  EXPECT_FALSE(OPENSSL_gmtime_adj(&copy, /*offset_day=*/0, /*offset_sec=*/1));
+
+  // Likewise for the earliest representable time.
+  tm min_time = {};
+  min_time.tm_year = 0 - 1900;
+  min_time.tm_mon = 1 - 1;
+  min_time.tm_mday = 1;
+  min_time.tm_hour = 0;
+  min_time.tm_min = 0;
+  min_time.tm_sec = 0;
+  copy = min_time;
+  EXPECT_TRUE(OPENSSL_gmtime_adj(&copy, /*offset_day=*/0, /*offset_sec=*/0));
+  EXPECT_EQ(TimeToTuple(copy), TimeToTuple(min_time));
+  EXPECT_FALSE(OPENSSL_gmtime_adj(&copy, /*offset_day=*/0, /*offset_sec=*/-1));
+
+  // Test we can offset between the minimum and maximum times.
+  const int64_t kValidTimeRange = 315569519999;
+  copy = min_time;
+  EXPECT_TRUE(OPENSSL_gmtime_adj(&copy, /*offset_day=*/0, kValidTimeRange));
+  EXPECT_EQ(TimeToTuple(copy), TimeToTuple(max_time));
+  EXPECT_TRUE(OPENSSL_gmtime_adj(&copy, /*offset_day=*/0, -kValidTimeRange));
+  EXPECT_EQ(TimeToTuple(copy), TimeToTuple(min_time));
+
+  // The second offset may even exceed kValidTimeRange if it is canceled out by
+  // offset_day.
+  EXPECT_TRUE(OPENSSL_gmtime_adj(&copy, /*offset_day=*/-1,
+                                 kValidTimeRange + 24 * 3600));
+  EXPECT_EQ(TimeToTuple(copy), TimeToTuple(max_time));
+  EXPECT_TRUE(OPENSSL_gmtime_adj(&copy, /*offset_day=*/1,
+                                 -kValidTimeRange - 24 * 3600));
+  EXPECT_EQ(TimeToTuple(copy), TimeToTuple(min_time));
+
+  // Make sure the internal calculations for |OPENSSL_gmtime_adj| stay in
+  // bounds.
+  copy = max_time;
+  EXPECT_FALSE(OPENSSL_gmtime_adj(&copy, INT_MAX, LONG_MAX));
+  copy = min_time;
+  EXPECT_FALSE(OPENSSL_gmtime_adj(&copy, INT_MIN, LONG_MIN));
+}
+
 // The ASN.1 macros do not work on Windows shared library builds, where usage of
 // |OPENSSL_EXPORT| is a bit stricter.
 #if !defined(OPENSSL_WINDOWS) || !defined(BORINGSSL_SHARED_LIBRARY)
diff --git a/crypto/asn1/internal.h b/crypto/asn1/internal.h
index 57e6966..601a3ca 100644
--- a/crypto/asn1/internal.h
+++ b/crypto/asn1/internal.h
@@ -87,7 +87,7 @@
 // must be in the range of year 0000 to 9999 both before and after the update or
 // a failure will be returned.
 OPENSSL_EXPORT int OPENSSL_gmtime_adj(struct tm *tm, int offset_day,
-                                      long offset_sec);
+                                      int64_t offset_sec);
 
 // OPENSSL_gmtime_diff calculates the difference between |from| and |to|. It
 // returns one, and outputs the difference as a number of days and seconds in
diff --git a/crypto/asn1/posix_time.c b/crypto/asn1/posix_time.c
index 2422bd3..ff95cca 100644
--- a/crypto/asn1/posix_time.c
+++ b/crypto/asn1/posix_time.c
@@ -26,12 +26,12 @@
 #include "internal.h"
 
 #define SECS_PER_HOUR (60 * 60)
-#define SECS_PER_DAY (24 * SECS_PER_HOUR)
+#define SECS_PER_DAY (INT64_C(24) * SECS_PER_HOUR)
 
 
 // Is a year/month/day combination valid, in the range from year 0000
 // to 9999?
-static int is_valid_date(int year, int month, int day) {
+static int is_valid_date(int64_t year, int64_t month, int64_t day) {
   if (day < 1 || month < 1 || year < 0 || year > 9999) {
     return 0;
   }
@@ -62,7 +62,7 @@
 
 // Is a time valid? Leap seconds of 60 are not considered valid, as
 // the POSIX time in seconds does not include them.
-static int is_valid_time(int hours, int minutes, int seconds) {
+static int is_valid_time(int64_t hours, int64_t minutes, int64_t seconds) {
   if (hours < 0 || minutes < 0 || seconds < 0 || hours > 23 || minutes > 59 ||
       seconds > 59) {
     return 0;
@@ -70,17 +70,22 @@
   return 1;
 }
 
-// Is a int64 time representing a time within our expected range?
-static int is_valid_epoch_time(int64_t time) {
-  // 0000-01-01 00:00:00 UTC to 9999-12-31 23:59:59 UTC
-  return (int64_t)-62167219200 <= time && time <= (int64_t)253402300799;
+// 0000-01-01 00:00:00 UTC
+#define MIN_POSIX_TIME INT64_C(-62167219200)
+// 9999-12-31 23:59:59 UTC
+#define MAX_POSIX_TIME INT64_C(253402300799)
+
+// Is an int64 time within our expected range?
+static int is_valid_posix_time(int64_t time) {
+  return MIN_POSIX_TIME <= time && time <= MAX_POSIX_TIME;
 }
 
 // Inspired by algorithms presented in
 // https://howardhinnant.github.io/date_algorithms.html
 // (Public Domain)
-static int posix_time_from_utc(int year, int month, int day, int hours,
-                               int minutes, int seconds, int64_t *out_time) {
+static int posix_time_from_utc(int64_t year, int64_t month, int64_t day,
+                               int64_t hours, int64_t minutes, int64_t seconds,
+                               int64_t *out_time) {
   if (!is_valid_date(year, month, day) ||
       !is_valid_time(hours, minutes, seconds)) {
     return 0;
@@ -108,7 +113,7 @@
 static int utc_from_posix_time(int64_t time, int *out_year, int *out_month,
                                int *out_day, int *out_hours, int *out_minutes,
                                int *out_seconds) {
-  if (!is_valid_epoch_time(time)) {
+  if (!is_valid_posix_time(time)) {
     return 0;
   }
   int64_t days = time / SECS_PER_DAY;
@@ -143,24 +148,21 @@
 }
 
 int OPENSSL_tm_to_posix(const struct tm *tm, int64_t *out) {
-  // Ensure the additions below do not overflow.
-  if (tm->tm_year > 9999 || tm->tm_mon > 12) {
-    return 0;
-  }
-
-  return posix_time_from_utc(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
-                             tm->tm_hour, tm->tm_min, tm->tm_sec, out);
+  return posix_time_from_utc(tm->tm_year + INT64_C(1900),
+                             tm->tm_mon + INT64_C(1), tm->tm_mday, tm->tm_hour,
+                             tm->tm_min, tm->tm_sec, out);
 }
 
 int OPENSSL_posix_to_tm(int64_t time, struct tm *out_tm) {
-  memset(out_tm, 0, sizeof(struct tm));
-  if (!utc_from_posix_time(time, &out_tm->tm_year, &out_tm->tm_mon,
-                           &out_tm->tm_mday, &out_tm->tm_hour, &out_tm->tm_min,
-                           &out_tm->tm_sec)) {
+  struct tm tmp_tm = {0};
+  if (!utc_from_posix_time(time, &tmp_tm.tm_year, &tmp_tm.tm_mon,
+                           &tmp_tm.tm_mday, &tmp_tm.tm_hour, &tmp_tm.tm_min,
+                           &tmp_tm.tm_sec)) {
     return 0;
   }
-  out_tm->tm_year -= 1900;
-  out_tm->tm_mon -= 1;
+  tmp_tm.tm_year -= 1900;
+  tmp_tm.tm_mon -= 1;
+  *out_tm = tmp_tm;
 
   return 1;
 }
@@ -192,43 +194,47 @@
   return out_tm;
 }
 
-int OPENSSL_gmtime_adj(struct tm *tm, int off_day, long offset_sec) {
+int OPENSSL_gmtime_adj(struct tm *tm, int offset_day, int64_t offset_sec) {
   int64_t posix_time;
-  if (!posix_time_from_utc(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
-                           tm->tm_hour, tm->tm_min, tm->tm_sec, &posix_time)) {
+  if (!OPENSSL_tm_to_posix(tm, &posix_time)) {
     return 0;
   }
-  if (!utc_from_posix_time(
-          posix_time + (int64_t)off_day * SECS_PER_DAY + offset_sec,
-          &tm->tm_year, &tm->tm_mon, &tm->tm_mday, &tm->tm_hour, &tm->tm_min,
-          &tm->tm_sec)) {
+  static_assert(INT_MAX <= INT64_MAX / SECS_PER_DAY,
+                "day offset in seconds cannot overflow");
+  static_assert(MAX_POSIX_TIME <= INT64_MAX - INT_MAX * SECS_PER_DAY,
+                "addition cannot overflow");
+  static_assert(MIN_POSIX_TIME >= INT64_MIN - INT_MIN * SECS_PER_DAY,
+                "addition cannot underflow");
+  posix_time += offset_day * SECS_PER_DAY;
+  if (posix_time > 0 && offset_sec > INT64_MAX - posix_time) {
     return 0;
   }
-  tm->tm_year -= 1900;
-  tm->tm_mon -= 1;
+  if (posix_time < 0 && offset_sec < INT64_MIN - posix_time) {
+    return 0;
+  }
+  posix_time += offset_sec;
+
+  if (!OPENSSL_posix_to_tm(posix_time, tm)) {
+    return 0;
+  }
 
   return 1;
 }
 
 int OPENSSL_gmtime_diff(int *out_days, int *out_secs, const struct tm *from,
                         const struct tm *to) {
-  int64_t time_to;
-  if (!posix_time_from_utc(to->tm_year + 1900, to->tm_mon + 1, to->tm_mday,
-                           to->tm_hour, to->tm_min, to->tm_sec, &time_to)) {
+  int64_t time_to, time_from;
+  if (!OPENSSL_tm_to_posix(to, &time_to) ||
+      !OPENSSL_tm_to_posix(from, &time_from)) {
     return 0;
   }
-  int64_t time_from;
-  if (!posix_time_from_utc(from->tm_year + 1900, from->tm_mon + 1,
-                           from->tm_mday, from->tm_hour, from->tm_min,
-                           from->tm_sec, &time_from)) {
-    return 0;
-  }
+  // Times are in range, so these calculations can not overflow.
+  static_assert(SECS_PER_DAY <= INT_MAX, "seconds per day does not fit in int");
+  static_assert((MAX_POSIX_TIME - MIN_POSIX_TIME) / SECS_PER_DAY <= INT_MAX,
+                "range of valid POSIX times, in days, does not fit in int");
   int64_t timediff = time_to - time_from;
   int64_t daydiff = timediff / SECS_PER_DAY;
   timediff %= SECS_PER_DAY;
-  if (daydiff > INT_MAX || daydiff < INT_MIN) {
-    return 0;
-  }
   *out_secs = (int)timediff;
   *out_days = (int)daydiff;
   return 1;