[43/59] Fix overflow checking in offtime

Message ID 20250105055750.1668721-44-eggert@cs.ucla.edu (mailing list archive)
State New
Headers
Series time: sync mktime from Gnulib |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Paul Eggert Jan. 5, 2025, 5:57 a.m. UTC
  * time/offtime.c: Include <stdckdint.h>.
(__offtime): New arg CORR, so that overflow cannot happen when
the caller subtracts it from OFFSET.  All callers changed.
Replace loops with straight-line code.
Do not set *TP unless successful.
Do not rely on undefined behavior when checking for year overflow.
Use narrower integers when computing mon and mday.
---
 include/time.h | 10 ++++----
 time/offtime.c | 64 +++++++++++++++++++++++---------------------------
 time/tzfile.c  |  2 +-
 time/tzset.c   |  4 ++--
 4 files changed, 37 insertions(+), 43 deletions(-)
  

Patch

diff --git a/include/time.h b/include/time.h
index 186ea657cb..e460037ee2 100644
--- a/include/time.h
+++ b/include/time.h
@@ -247,12 +247,12 @@  extern int __gettimeofday64 (struct __timeval64 *restrict tv,
 libc_hidden_proto (__gettimeofday64)
 #endif
 
-/* Compute the `struct tm' representation of T,
-   offset OFFSET seconds east of UTC,
-   and store year, yday, mon, mday, wday, hour, min, sec into *TP.
-   Return TP if successful, a null pointer otherwise.  */
+/* Compute the 'struct tm' representation of T, offset OFFSET seconds
+   east of UTC and minus a leap second correction CORR.
+   If successful, store year, yday, mon, mday, wday, hour, min, sec
+   into *TP and return TP; otherwise return a null pointer.  */
 extern struct tm *__offtime (__time64_t __timer,
-			     long int __offset,
+			     long int __offset, int __corr,
 			     struct tm *__tp) attribute_hidden;
 
 extern char *__asctime_r (const struct tm *__tp, char *__buf)
diff --git a/time/offtime.c b/time/offtime.c
index 89cdcc4b15..9fce47c01d 100644
--- a/time/offtime.c
+++ b/time/offtime.c
@@ -16,43 +16,28 @@ 
    <https://www.gnu.org/licenses/>.  */
 
 #include <errno.h>
+#include <stdckdint.h>
 #include <time.h>
 
 #define	SECS_PER_HOUR	(60 * 60)
 #define	SECS_PER_DAY	(SECS_PER_HOUR * 24)
 
-/* Compute the `struct tm' representation of T,
-   offset OFFSET seconds east of UTC,
-   and store year, yday, mon, mday, wday, hour, min, sec into *TP.
-   Return TP if successful, a null pointer otherwise.  */
+/* Compute the 'struct tm' representation of T, offset OFFSET seconds
+   east of UTC and minus a leap second correction CORR.
+   If successful, store year, yday, mon, mday, wday, hour, min, sec
+   into *TP and return TP; otherwise return a null pointer.  */
 struct tm *
-__offtime (__time64_t t, long int offset, struct tm *tp)
+__offtime (__time64_t t, long int offset, int corr, struct tm *tp)
 {
-  __time64_t days, rem, y;
-  const unsigned short int *ip;
+  int rem = (t % SECS_PER_DAY + offset % SECS_PER_DAY - corr % SECS_PER_DAY
+	     + 3 * SECS_PER_DAY);
+  __time64_t days = (t / SECS_PER_DAY + offset / SECS_PER_DAY
+		     - corr / SECS_PER_DAY + rem / SECS_PER_DAY - 3);
 
-  days = t / SECS_PER_DAY;
-  rem = t % SECS_PER_DAY;
-  rem += offset;
-  while (rem < 0)
-    {
-      rem += SECS_PER_DAY;
-      --days;
-    }
-  while (rem >= SECS_PER_DAY)
-    {
-      rem -= SECS_PER_DAY;
-      ++days;
-    }
-  tp->tm_hour = rem / SECS_PER_HOUR;
-  rem %= SECS_PER_HOUR;
-  tp->tm_min = rem / 60;
-  tp->tm_sec = rem % 60;
   /* January 1, 1970 was a Thursday.  */
-  tp->tm_wday = (4 + days) % 7;
-  if (tp->tm_wday < 0)
-    tp->tm_wday += 7;
-  y = 1970;
+  int wday_rem = (4 + days) % 7;
+
+  __time64_t y = 1970;
 
 #define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
 #define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
@@ -68,19 +53,28 @@  __offtime (__time64_t t, long int offset, struct tm *tp)
 	       - LEAPS_THRU_END_OF (y - 1));
       y = yg;
     }
-  tp->tm_year = y - 1900;
-  if (tp->tm_year != y - 1900)
+  int year;
+  if (ckd_sub (&year, y, 1900))
     {
       /* The year cannot be represented due to overflow.  */
       __set_errno (EOVERFLOW);
       return NULL;
     }
+
+  int day_rem = rem % SECS_PER_DAY;
+  tp->tm_hour = day_rem / SECS_PER_HOUR;
+  int hour_rem = day_rem % SECS_PER_HOUR;
+  tp->tm_sec = hour_rem % 60;
+  tp->tm_min = hour_rem / 60;
+  tp->tm_year = year;
+  tp->tm_wday = wday_rem + (wday_rem < 0 ? 7 : 0);
   tp->tm_yday = days;
-  ip = __mon_yday[__isleap(y)];
-  for (y = 11; days < (long int) ip[y]; --y)
+  unsigned short int yday = days;
+  unsigned short int const *ip = __mon_yday[__isleap (y)];
+  int mon;
+  for (mon = 11; yday < ip[mon]; mon--)
     continue;
-  days -= ip[y];
-  tp->tm_mon = y;
-  tp->tm_mday = days + 1;
+  tp->tm_mon = mon;
+  tp->tm_mday = yday - ip[mon] + 1;
   return tp;
 }
diff --git a/time/tzfile.c b/time/tzfile.c
index 9c05811e22..01821cc368 100644
--- a/time/tzfile.c
+++ b/time/tzfile.c
@@ -648,7 +648,7 @@  __tzfile_compute (__time64_t timer, int use_localtime,
 
 	  /* Convert to broken down structure.  If this fails do not
 	     use the string.  */
-	  if (__glibc_unlikely (__offtime (timer, 0, tp) == NULL))
+	  if (__glibc_unlikely (__offtime (timer, 0, 0, tp) == NULL))
 	    goto use_last;
 
 	  /* Use the rules from the TZ string to compute the change.  */
diff --git a/time/tzset.c b/time/tzset.c
index 671394f02d..add0073e0d 100644
--- a/time/tzset.c
+++ b/time/tzset.c
@@ -610,7 +610,7 @@  __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
 		      &leap_extra_sec, tp);
   else
     {
-      tp = __offtime (timer, 0, tp);
+      tp = __offtime (timer, 0, 0, tp);
       if (tp && use_localtime)
 	__tz_compute (timer, tp);
       leap_correction = 0;
@@ -626,7 +626,7 @@  __tz_convert (__time64_t timer, int use_localtime, struct tm *tp)
 	  tp->tm_gmtoff = 0L;
 	}
 
-      if (__offtime (timer, tp->tm_gmtoff - leap_correction, tp) != NULL)
+      if (__offtime (timer, tp->tm_gmtoff, leap_correction, tp) != NULL)
 	{
 	  /* This assumes leap seconds can occur only when the local
 	     time offset from UTC is a multiple of 60 seconds,