* NEWS: Mention this.
* localtime.c (int_fast32_2s): New type.
(detzcode): Return it instead of returning int_fast32_t,
and do not silently pretend that -2**31 is -2**31 + 1 on
platforms that are not two’s complement.
All callers changed.
(tzloadbody): Reject TZif files containing a UT offset of -2**31.
Internet RFC 9636 prohibits that, and it can cause undefined
behavior both here and in callers.
---
 NEWS        |  6 ++++--
 localtime.c | 46 +++++++++++++++++++++++++++++++---------------
 2 files changed, 35 insertions(+), 17 deletions(-)

diff --git a/NEWS b/NEWS
index b4f3ead8..30f757df 100644
--- a/NEWS
+++ b/NEWS
@@ -26,8 +26,10 @@ Unreleased, experimental changes
     zic no longer assumes you can fflush a read-only stream.
     (Problem reported by Christos Zoulas.)
 
-    zic no longer generates UT offsets equal to -2**31, as RFC 9636
-    prohibits them.
+    zic no longer generates UT offsets equal to -2**31 and localtime.c
+    no longer accepts them, as they can cause trouble in both
+    localtime.c and its callers.  RFC 9636 prohibits such offsets.
+
 
 Release 2025c - 2025-12-10 14:42:37 -0800
 
diff --git a/localtime.c b/localtime.c
index 355822d2..a506f097 100644
--- a/localtime.c
+++ b/localtime.c
@@ -496,6 +496,16 @@ typedef ptrdiff_t desigidx_type;
 # error "TZNAME_MAXIMUM too large"
 #endif
 
+/* A type that can represent any 32-bit two's complement integer,
+   i.e., any integer in the range -2**31 .. 2**31 - 1.
+   Ordinarily this is int_fast32_t, but on non-C23 hosts
+   that are not two's complement it is int_fast64_t.  */
+#if INT_FAST32_MIN < -TWO_31_MINUS_1
+typedef int_fast32_t int_fast32_2s;
+#else
+typedef int_fast64_t int_fast32_2s;
+#endif
+
 struct ttinfo {                                /* time type information */
        int_least32_t   tt_utoff;       /* UT offset in seconds */
        desigidx_type   tt_desigidx;    /* abbreviation list index */
@@ -638,15 +648,14 @@ ttunspecified(struct state const *sp, int i)
   return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0;
 }
 
-static int_fast32_t
+static int_fast32_2s
 detzcode(const char *const codep)
 {
-       register int_fast32_t   result;
        register int            i;
-       int_fast32_t one = 1;
-       int_fast32_t halfmaxval = one << (32 - 2);
-       int_fast32_t maxval = halfmaxval - 1 + halfmaxval;
-       int_fast32_t minval = -1 - maxval;
+       int_fast32_2s
+         maxval = TWO_31_MINUS_1,
+         minval = -1 - maxval,
+         result;
 
        result = codep[0] & 0x7f;
        for (i = 1; i < 4; ++i)
@@ -654,8 +663,7 @@ detzcode(const char *const codep)
 
        if (codep[0] & 0x80) {
          /* Do two's-complement negation even on non-two's-complement machines.
-            If the result would be minval - 1, return minval.  */
-         result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0;
+            This cannot overflow, as int_fast32_2s is wide enough.  */
          result += minval;
        }
        return result;
@@ -1033,14 +1041,15 @@ tzloadbody(char const *name, struct state *sp, char 
tzloadflags,
            char version = up->tzhead.tzh_version[0];
            bool skip_datablock = stored == 4 && version;
            int_fast32_t datablock_size;
-           int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt);
-           int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt);
            int_fast64_t prevtr = -1;
            int_fast32_t prevcorr;
-           int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt);
-           int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt);
-           int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt);
-           int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt);
+           int_fast32_2s
+             ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt),
+             ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt),
+             leapcnt = detzcode(up->tzhead.tzh_leapcnt),
+             timecnt = detzcode(up->tzhead.tzh_timecnt),
+             typecnt = detzcode(up->tzhead.tzh_typecnt),
+             charcnt = detzcode(up->tzhead.tzh_charcnt);
            char const *p = up->buf + tzheadsize;
            /* Although tzfile(5) currently requires typecnt to be nonzero,
               support future formats that may allow zero typecnt
@@ -1109,9 +1118,16 @@ tzloadbody(char const *name, struct state *sp, char 
tzloadflags,
                for (i = 0; i < sp->typecnt; ++i) {
                        register struct ttinfo *        ttisp;
                        unsigned char isdst, desigidx;
+                       int_fast32_2s utoff = detzcode(p);
+
+                       /* Reject a UT offset equal to -2**31, as it might
+                          cause trouble both in this file and in callers.
+                          Also, it violates RFC 9636 section 3.2.  */
+                       if (utoff < -TWO_31_MINUS_1)
+                         return EINVAL;
 
                        ttisp = &sp->ttis[i];
-                       ttisp->tt_utoff = detzcode(p);
+                       ttisp->tt_utoff = utoff;
                        p += 4;
                        isdst = *p++;
                        if (! (isdst < 2))
-- 
2.51.0

Reply via email to