* 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