about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2015-10-14 18:58:15 -0400
committerRich Felker <dalias@aerifal.cx>2015-10-14 18:58:15 -0400
commit8d93cb57754f1f81f8e15bb74afd1725cead45c2 (patch)
treeb7a3a82bcdc031f34180c168726cd08e3ee7600b
parent6fef8cafbd0f6f185897bc87feb1ff66e2e204e1 (diff)
downloadmusl-8d93cb57754f1f81f8e15bb74afd1725cead45c2.tar.gz
musl-8d93cb57754f1f81f8e15bb74afd1725cead45c2.tar.xz
musl-8d93cb57754f1f81f8e15bb74afd1725cead45c2.zip
fix strftime handling of out-of-range struct tm fields
strftime results are unspecified in this case, but should not invoke
undefined behaviour.

tm_wday, tm_yday, tm_mon and tm_year fields were used in signed int
arithmetic that could overflow.

based on patch by Szabolcs Nagy.
-rw-r--r--src/time/strftime.c20
1 files changed, 12 insertions, 8 deletions
diff --git a/src/time/strftime.c b/src/time/strftime.c
index e945bb7d..f1ccc4dd 100644
--- a/src/time/strftime.c
+++ b/src/time/strftime.c
@@ -21,24 +21,24 @@ static int is_leap(int y)
 
 static int week_num(const struct tm *tm)
 {
-	int val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7;
+	int val = (tm->tm_yday + 7U - (tm->tm_wday+6U)%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 ((tm->tm_wday + 371U - tm->tm_yday - 2) % 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;
+		int dec31 = (tm->tm_wday + 7U - tm->tm_yday - 1) % 7;
 		if (dec31 == 4 || (dec31 == 5 && is_leap(tm->tm_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;
+		int jan1 = (tm->tm_wday + 371U - tm->tm_yday) % 7;
 		if (jan1 != 4 && (jan1 != 3 || !is_leap(tm->tm_year)))
 			val = 1;
 	}
@@ -52,21 +52,25 @@ const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm *
 {
 	nl_item item;
 	long long val;
-	const char *fmt;
+	const char *fmt = "-";
 	int width = 2;
 
 	switch (f) {
 	case 'a':
+		if (tm->tm_wday > 6U) goto string;
 		item = ABDAY_1 + tm->tm_wday;
 		goto nl_strcat;
 	case 'A':
+		if (tm->tm_wday > 6U) goto string;
 		item = DAY_1 + tm->tm_wday;
 		goto nl_strcat;
 	case 'h':
 	case 'b':
+		if (tm->tm_mon > 11U) goto string;
 		item = ABMON_1 + tm->tm_mon;
 		goto nl_strcat;
 	case 'B':
+		if (tm->tm_mon > 11U) goto string;
 		item = MON_1 + tm->tm_mon;
 		goto nl_strcat;
 	case 'c':
@@ -143,10 +147,10 @@ const char *__strftime_fmt_1(char (*s)[100], size_t *l, int f, const struct tm *
 		width = 1;
 		goto number;
 	case 'U':
-		val = (tm->tm_yday + 7 - tm->tm_wday) / 7;
+		val = (tm->tm_yday + 7U - tm->tm_wday) / 7;
 		goto number;
 	case 'W':
-		val = (tm->tm_yday + 7 - (tm->tm_wday+6)%7) / 7;
+		val = (tm->tm_yday + 7U - (tm->tm_wday+6U)%7) / 7;
 		goto number;
 	case 'V':
 		val = week_num(tm);
@@ -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;