From 67ece14d68a5b086f2b4a04d27b0d48f3f47bea3 Mon Sep 17 00:00:00 2001
From: Paul Eggert
Date: Fri, 9 Nov 2018 17:33:24 0800
Subject: [PATCH 6/7] mktime: fix nonEOVERFLOW errno handling
[BZ#23789]
mktime was not properly reporting failures when the underlying
localtime_r fails with errno != EOVERFLOW; it incorrectly treated
them like EOVERFLOW failures, and set errno to EOVERFLOW.
The problem could happen on nonglibc platforms, with Gnulib.
* time/mktime.c (guess_time_tm): Remove, replacing with ...
(tm_diff): ... this simpler function, which does not change errno.
All callers changed to deal with errno themselves.
(ranged_convert, __mktime_internal): Return failure immediately if
the underlying function reports any failure other than EOVERFLOW.
(__mktime_internal): Set errno to EOVERFLOW if the springforward
gap code fails.

ChangeLog  14 ++++
time/mktime.c  197 +++++++++++++++++++++++++
2 files changed, 113 insertions(+), 98 deletions()
diff git a/ChangeLog b/ChangeLog
index 939ca140ef..9f62ab865e 100644
 a/ChangeLog
+++ b/ChangeLog
@@ 1,5 +1,19 @@
20181109 Paul Eggert
+ mktime: fix nonEOVERFLOW errno handling
+ [BZ#23789]
+ mktime was not properly reporting failures when the underlying
+ localtime_r fails with errno != EOVERFLOW; it incorrectly treated
+ them like EOVERFLOW failures, and set errno to EOVERFLOW.
+ The problem could happen on nonglibc platforms, with Gnulib.
+ * time/mktime.c (guess_time_tm): Remove, replacing with ...
+ (tm_diff): ... this simpler function, which does not change errno.
+ All callers changed to deal with errno themselves.
+ (ranged_convert, __mktime_internal): Return failure immediately if
+ the underlying function reports any failure other than EOVERFLOW.
+ (__mktime_internal): Set errno to EOVERFLOW if the springforward
+ gap code fails.
+
mktime: fix bug with Y2038 DST transition
[BZ#23789]
* time/mktime.c (ranged_convert): On 32bit platforms, donâ€™t
diff git a/time/mktime.c b/time/mktime.c
index 6d5b8cf838..dc83985bbd 100644
 a/time/mktime.c
+++ b/time/mktime.c
@@ 250,45 +250,25 @@ long_int_avg (long_int a, long_int b)
return shr (a, 1) + shr (b, 1) + ((a  b) & 1);
}
/* Return a time_t value corresponding to (YEARYDAY HOUR:MIN:SEC),
 assuming that T corresponds to *TP and that no clock adjustments
 occurred between *TP and the desired time.
 Although T and the returned value are of type long_int,
 they represent time_t values and must be in time_t range.
 If TP is null, return a value not equal to T; this avoids false matches.
+/* Return a long_int value corresponding to (YEARYDAY HOUR:MIN:SEC)
+ minus *TP seconds, assuming no clock adjustments occurred between
+ the two timestamps.
+
YEAR and YDAY must not be so large that multiplying them by three times the
number of seconds in a year (or day, respectively) would overflow long_int.
 If TP is nonnull and the returned value would be out of range, set
 errno to EOVERFLOW and yield a minimal or maximal inrange value
 that is not equal to T. */
+ *TP should be in the usual range. */
static long_int
guess_time_tm (long_int year, long_int yday, int hour, int min, int sec,
 long_int t, const struct tm *tp)
+tm_diff (long_int year, long_int yday, int hour, int min, int sec,
+ struct tm const *tp)
{
 if (tp)
 {
 long_int result;
 long_int d = ydhms_diff (year, yday, hour, min, sec,
 tp>tm_year, tp>tm_yday,
 tp>tm_hour, tp>tm_min, tp>tm_sec);
 if (! INT_ADD_WRAPV (t, d, &result))
 return result;
 __set_errno (EOVERFLOW);
 }

 /* An error occurred, probably overflow. Return the nearest result
 that is actually in range, except don't report a zero difference
 if the actual difference is nonzero, as that would cause a false
 match; and don't oscillate between two values, as that would
 confuse the springforward gap detector. */
 return (t < long_int_avg (mktime_min, mktime_max)
 ? (t <= mktime_min + 1 ? t + 1 : mktime_min)
 : (mktime_max  1 <= t ? t  1 : mktime_max));
+ return ydhms_diff (year, yday, hour, min, sec,
+ tp>tm_year, tp>tm_yday,
+ tp>tm_hour, tp>tm_min, tp>tm_sec);
}
/* Use CONVERT to convert T to a struct tm value in *TM. T must be in
 range for time_t. Return TM if successful, NULL if T is out of
 range for CONVERT. */
+ range for time_t. Return TM if successful, NULL (setting errno) on
+ failure. */
static struct tm *
convert_time (struct tm *(*convert) (const time_t *, struct tm *),
long_int t, struct tm *tm)
@@ 300,49 +280,48 @@ convert_time (struct tm *(*convert) (const time_t *, struct tm *),
/* Use CONVERT to convert *T to a broken down time in *TP.
If *T is out of range for conversion, adjust it so that
it is the nearest inrange value and then convert that.
 A value is in range if it fits in both time_t and long_int. */
+ A value is in range if it fits in both time_t and long_int.
+ Return TP on success, NULL (setting errno) on failure. */
static struct tm *
ranged_convert (struct tm *(*convert) (const time_t *, struct tm *),
long_int *t, struct tm *tp)
{
 struct tm *r;
 if (*t < mktime_min)
 *t = mktime_min;
 else if (mktime_max < *t)
 *t = mktime_max;
 r = convert_time (convert, *t, tp);

 if (!r && *t)
+ long_int t1 = (*t < mktime_min ? mktime_min
+ : *t <= mktime_max ? *t : mktime_max);
+ struct tm *r = convert_time (convert, t1, tp);
+ if (r)
{
 long_int bad = *t;
 long_int ok = 0;

 /* BAD is a known unconvertible value, and OK is a known good one.
 Use binary search to narrow the range between BAD and OK until
 they differ by 1. */
 while (true)
 {
 long_int mid = long_int_avg (ok, bad);
 if (mid == ok  mid == bad)
 break;
 r = convert_time (convert, mid, tp);
 if (r)
 ok = mid;
 else
 bad = mid;
 }
+ *t = t1;
+ return r;
+ }
+ if (errno != EOVERFLOW)
+ return NULL;
 *t = ok;
+ long_int bad = t1;
+ long_int ok = 0;
+ struct tm oktm; oktm.tm_sec = 1;
 if (!r && ok)
 {
 /* The last conversion attempt failed;
 revert to the most recent successful attempt. */
 r = convert_time (convert, ok, tp);
 }
+ /* BAD is a known outofrange value, and OK is a known inrange one.
+ Use binary search to narrow the range between BAD and OK until
+ they differ by 1. */
+ while (true)
+ {
+ long_int mid = long_int_avg (ok, bad);
+ if (mid == ok  mid == bad)
+ break;
+ if (convert_time (convert, mid, tp))
+ ok = mid, oktm = *tp;
+ else if (errno != EOVERFLOW)
+ return NULL;
+ else
+ bad = mid;
}
 return r;
+ if (oktm.tm_sec < 0)
+ return NULL;
+ *t = ok;
+ *tp = oktm;
+ return tp;
}
@@ 359,7 +338,6 @@ __mktime_internal (struct tm *tp,
struct tm *(*convert) (const time_t *, struct tm *),
mktime_offset_t *offset)
{
 long_int t, gt, t0, t1, t2;
struct tm tm;
/* The maximum number of probes (calls to CONVERT) should be enough
@@ 379,7 +357,7 @@ __mktime_internal (struct tm *tp,
int isdst = tp>tm_isdst;
/* 1 if the previous probe was DST. */
 int dst2;
+ int dst2 = 0;
/* Ensure that mon is in range, and set year accordingly. */
int mon_remainder = mon % 12;
@@ 418,36 +396,46 @@ __mktime_internal (struct tm *tp,
time. */
INT_SUBTRACT_WRAPV (0, off, &negative_offset_guess);
 t0 = ydhms_diff (year, yday, hour, min, sec,
 EPOCH_YEAR  TM_YEAR_BASE, 0, 0, 0, negative_offset_guess);
+ long_int t0 = ydhms_diff (year, yday, hour, min, sec,
+ EPOCH_YEAR  TM_YEAR_BASE, 0, 0, 0,
+ negative_offset_guess);
+ long_int t = t0, t1 = t0, t2 = t0;
/* Repeatedly use the error to improve the guess. */
 for (t = t1 = t2 = t0, dst2 = 0;
 (gt = guess_time_tm (year, yday, hour, min, sec, t,
 ranged_convert (convert, &t, &tm)),
 t != gt);
 t1 = t2, t2 = t, t = gt, dst2 = tm.tm_isdst != 0)
 if (t == t1 && t != t2
 && (tm.tm_isdst < 0
  (isdst < 0
 ? dst2 <= (tm.tm_isdst != 0)
 : (isdst != 0) != (tm.tm_isdst != 0))))
 /* We can't possibly find a match, as we are oscillating
 between two values. The requested time probably falls
 within a springforward gap of size GT  T. Follow the common
 practice in this case, which is to return a time that is GT  T
 away from the requested time, preferring a time whose
 tm_isdst differs from the requested value. (If no tm_isdst
 was requested and only one of the two values has a nonzero
 tm_isdst, prefer that value.) In practice, this is more
 useful than returning 1. */
 goto offset_found;
 else if (remaining_probes == 0)
 {
 __set_errno (EOVERFLOW);
+ while (true)
+ {
+ if (! ranged_convert (convert, &t, &tm))
return 1;
 }
+ long_int dt = tm_diff (year, yday, hour, min, sec, &tm);
+ if (dt == 0)
+ break;
+
+ if (t == t1 && t != t2
+ && (tm.tm_isdst < 0
+  (isdst < 0
+ ? dst2 <= (tm.tm_isdst != 0)
+ : (isdst != 0) != (tm.tm_isdst != 0))))
+ /* We can't possibly find a match, as we are oscillating
+ between two values. The requested time probably falls
+ within a springforward gap of size DT. Follow the common
+ practice in this case, which is to return a time that is DT
+ away from the requested time, preferring a time whose
+ tm_isdst differs from the requested value. (If no tm_isdst
+ was requested and only one of the two values has a nonzero
+ tm_isdst, prefer that value.) In practice, this is more
+ useful than returning 1. */
+ goto offset_found;
+
+ remaining_probes;
+ if (remaining_probes == 0)
+ {
+ __set_errno (EOVERFLOW);
+ return 1;
+ }
+
+ t1 = t2, t2 = t, t += dt, dst2 = tm.tm_isdst != 0;
+ }
/* We have a match. Check whether tm.tm_isdst has the requested
value, if any. */
@@ 489,17 +477,30 @@ __mktime_internal (struct tm *tp,
if (! INT_ADD_WRAPV (t, delta * direction, &ot))
{
struct tm otm;
 ranged_convert (convert, &ot, &otm);
+ if (! ranged_convert (convert, &ot, &otm))
+ return 1;
if (! isdst_differ (isdst, otm.tm_isdst))
{
/* We found the desired tm_isdst.
Extrapolate back to the desired time. */
 t = guess_time_tm (year, yday, hour, min, sec, ot, &otm);
 ranged_convert (convert, &t, &tm);
 goto offset_found;
+ long_int gt = ot + tm_diff (year, yday, hour, min, sec,
+ &otm);
+ if (mktime_min <= gt && gt <= mktime_max)
+ {
+ if (convert_time (convert, gt, &tm))
+ {
+ t = gt;
+ goto offset_found;
+ }
+ if (errno != EOVERFLOW)
+ return 1;
+ }
}
}
}
+
+ __set_errno (EOVERFLOW);
+ return 1;
}
offset_found:

2.19.1