>From 042305a745991d2de8e7fd0111f158086d283f35 Mon Sep 17 00:00:00 2001 From: Szabolcs Nagy Date: Sun, 20 Sep 2015 12:02:21 +0000 Subject: [PATCH] fix strftime to handle out of range tm fields without UB Calling strftime with out of range tm fields is not undefined behaviour, it should return a result (though in this case the stored string is unspecified). tm_wday, tm_yday, tm_mon and tm_year fields are used in signed int arithmetics that may overflow. For tm_year long long arithmetics is used and the rest is fixed by limiting the input. --- src/time/strftime.c | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/time/strftime.c b/src/time/strftime.c index e945bb7..464b546 100644 --- a/src/time/strftime.c +++ b/src/time/strftime.c @@ -19,27 +19,27 @@ static int is_leap(int y) return !(y%4) && ((y%100) || !(y%400)); } -static int week_num(const struct tm *tm) +static int week_num(int wday, int yday, int year) { - int val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7; + int val = (yday + 7 - (wday+6)%7) / 7; /* If 1 Jan is just 1-3 days past Monday, * the previous week is also in this year. */ - if ((tm->tm_wday - tm->tm_yday - 2 + 371) % 7 <= 2) + if ((wday - yday - 2 + 371) % 7 <= 2) val++; if (!val) { val = 52; /* If 31 December of prev year a Thursday, * or Friday of a leap year, then the * prev year has 53 weeks. */ - int dec31 = (tm->tm_wday - tm->tm_yday - 1 + 7) % 7; - if (dec31 == 4 || (dec31 == 5 && is_leap(tm->tm_year%400-1))) + int dec31 = (wday - yday - 1 + 7) % 7; + if (dec31 == 4 || (dec31 == 5 && is_leap(year%400-1))) val++; } else if (val == 53) { /* If 1 January is not a Thursday, and not * a Wednesday of a leap year, then this * year has only 52 weeks. */ - int jan1 = (tm->tm_wday - tm->tm_yday + 371) % 7; - if (jan1 != 4 && (jan1 != 3 || !is_leap(tm->tm_year))) + int jan1 = (wday - yday + 371) % 7; + if (jan1 != 4 && (jan1 != 3 || !is_leap(year))) val = 1; } return val; @@ -55,19 +55,23 @@ const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm * const char *fmt; int width = 2; + int wday = tm->tm_wday < 7U ? tm->tm_wday : 0; + int yday = tm->tm_yday < 366U ? tm->tm_yday : 0; + int mon = tm->tm_mon < 12U ? tm->tm_mon : 0; + switch (f) { case 'a': - item = ABDAY_1 + tm->tm_wday; + item = ABDAY_1 + wday; goto nl_strcat; case 'A': - item = DAY_1 + tm->tm_wday; + item = DAY_1 + wday; goto nl_strcat; case 'h': case 'b': - item = ABMON_1 + tm->tm_mon; + item = ABMON_1 + mon; goto nl_strcat; case 'B': - item = MON_1 + tm->tm_mon; + item = MON_1 + mon; goto nl_strcat; case 'c': item = D_T_FMT; @@ -90,8 +94,8 @@ const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm * case 'g': case 'G': val = tm->tm_year + 1900LL; - if (tm->tm_yday < 3 && week_num(tm) != 1) val--; - else if (tm->tm_yday > 360 && week_num(tm) == 1) val++; + if (yday < 3 && week_num(wday, yday, tm->tm_year) != 1) val--; + else if (yday > 360 && week_num(wday, yday, tm->tm_year) == 1) val++; if (f=='g') val %= 100; else width = 4; goto number; @@ -104,11 +108,11 @@ const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm * else if (val > 12) val -= 12; goto number; case 'j': - val = tm->tm_yday+1; + val = yday+1; width = 3; goto number; case 'm': - val = tm->tm_mon+1; + val = mon+1; goto number; case 'M': val = tm->tm_min; @@ -139,20 +143,20 @@ const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm * fmt = "%H:%M:%S"; goto recu_strftime; case 'u': - val = tm->tm_wday ? tm->tm_wday : 7; + val = wday ? wday : 7; width = 1; goto number; case 'U': - val = (tm->tm_yday + 7 - tm->tm_wday) / 7; + val = (yday + 7 - wday) / 7; goto number; case 'W': - val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7; + val = (yday + 7 - (wday+6)%7) / 7; goto number; case 'V': - val = week_num(tm); + val = week_num(wday, yday, tm->tm_year); goto number; case 'w': - val = tm->tm_wday; + val = wday; width = 1; goto number; case 'x': @@ -165,7 +169,7 @@ const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm * val = tm->tm_year % 100; goto number; case 'Y': - val = tm->tm_year + 1900; + val = tm->tm_year + 1900LL; if (val >= 10000) { *l = snprintf(*s, sizeof *s, "+%lld", val); return *s; -- 2.4.1