From db9347fbbc17f7e9855490c8c4cf54982fc5871e Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sun, 20 Feb 2022 13:09:30 -0800
Subject: [PROPOSED 1/2] Fix processing of TZif truncation

Problem reported by Almaz Mingaleev in:
https://mm.icann.org/pipermail/tz/2022-February/031198.html
* localtime.c (UNSPEC, CHARS_EXTRA): New constants.
(struct state): Make 'chars' likely a bit bigger.
(ttunspecified): New function.
(tzloadbody): Make it safe to call ttunspecified.
(tzloadbody, time2sub, time1):
Skip unspecified local times when inferring.
---
 NEWS        |  3 +++
 localtime.c | 33 ++++++++++++++++++++++++++++-----
 2 files changed, 31 insertions(+), 5 deletions(-)

diff --git a/NEWS b/NEWS
index 5f1a557..c847d53 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,9 @@ Unreleased, experimental changes
 
   Changes to code
 
+    Fix bug when mktime gets confused by truncated TZif files with
+    unspecified local time.  (Problem reported by Almaz Mingaleev.)
+
     Fix bug when 32-bit time_t code reads malformed 64-bit TZif data.
     (Problem reported by Christos Zoulas.)
 
diff --git a/localtime.c b/localtime.c
index 39ef6e3..913c7e8 100644
--- a/localtime.c
+++ b/localtime.c
@@ -105,6 +105,15 @@ struct lsinfo {				/* leap second information */
 #define SMALLEST(a, b)	(((a) < (b)) ? (a) : (b))
 #define BIGGEST(a, b)	(((a) > (b)) ? (a) : (b))
 
+/* This abbreviation means local time is unspecified.  */
+static char const UNSPEC[] = "-00";
+
+/* How many extra bytes are needed at the end of struct state's chars array.
+   This needs to be at least 1 for null termination in case the input
+   data isn't properly terminated, and it also needs to be big enough
+   for ttunspecified to work without crashing.  */
+enum { CHARS_EXTRA = BIGGEST(sizeof UNSPEC, 2) - 1 };
+
 #ifdef TZNAME_MAX
 #define MY_TZNAME_MAX	TZNAME_MAX
 #endif /* defined TZNAME_MAX */
@@ -122,7 +131,8 @@ struct state {
 	time_t		ats[TZ_MAX_TIMES];
 	unsigned char	types[TZ_MAX_TIMES];
 	struct ttinfo	ttis[TZ_MAX_TYPES];
-	char		chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof gmt),
+	char		chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + CHARS_EXTRA,
+					      sizeof gmt),
 				(2 * (MY_TZNAME_MAX + 1)))];
 	struct lsinfo	lsis[TZ_MAX_LEAPS];
 
@@ -211,6 +221,15 @@ init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx)
   s->tt_ttisut = false;
 }
 
+/* Return true if SP's time type I does not specify local time.  */
+static bool
+ttunspecified(struct state const *sp, int i)
+{
+  char const *abbr = &sp->chars[sp->ttis[i].tt_desigidx];
+  /* memcmp is likely faster than strcmp, and is safe due to CHARS_EXTRA.  */
+  return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0;
+}
+
 static int_fast32_t
 detzcode(const char *const codep)
 {
@@ -528,7 +547,9 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
 		}
 		for (i = 0; i < sp->charcnt; ++i)
 			sp->chars[i] = *p++;
-		sp->chars[i] = '\0';	/* ensure '\0' at end */
+		/* Ensure '\0'-terminated, and make it safe to call
+		   ttunspecified later.  */
+		memset(&sp->chars[i], 0, CHARS_EXTRA);
 
 		/* Read leap seconds, discarding those out of time_t range.  */
 		leapcnt = 0;
@@ -698,13 +719,13 @@ tzloadbody(char const *name, struct state *sp, bool doextend,
 	   standard-time type.  See:
 	   https://mm.icann.org/pipermail/tz/2013-May/019368.html */
 	/*
-	** If type 0 is unused in transitions,
+	** If type 0 does not specify local time, or is unused in transitions,
 	** it's the type to use for early times.
 	*/
 	for (i = 0; i < sp->timecnt; ++i)
 		if (sp->types[i] == 0)
 			break;
-	i = i < sp->timecnt ? -1 : 0;
+	i = i < sp->timecnt && ! ttunspecified(sp, 0) ? -1 : 0;
 	/*
 	** Absent the above,
 	** if there are transition times
@@ -2089,6 +2110,8 @@ time2sub(struct tm *const tmp,
 			for (j = sp->typecnt - 1; j >= 0; --j) {
 				if (sp->ttis[j].tt_isdst == yourtm.tm_isdst)
 					continue;
+				if (ttunspecified(sp, j))
+				  continue;
 				newt = (t + sp->ttis[j].tt_utoff
 					- sp->ttis[i].tt_utoff);
 				if (! funcp(sp, &newt, offset, &mytm))
@@ -2181,7 +2204,7 @@ time1(struct tm *const tmp,
 		seen[i] = false;
 	nseen = 0;
 	for (i = sp->timecnt - 1; i >= 0; --i)
-		if (!seen[sp->types[i]]) {
+		if (!seen[sp->types[i]] && !ttunspecified(sp, sp->types[i])) {
 			seen[sp->types[i]] = true;
 			types[nseen++] = sp->types[i];
 		}
-- 
2.32.0

