Module Name:    src
Committed By:   martin
Date:           Sat Aug 24 16:15:40 UTC 2024

Modified Files:
        src/lib/libc/time [netbsd-9]: strptime.c
        src/tests/lib/libc/time [netbsd-9]: t_strptime.c

Log Message:
Pull up following revision(s) (requested by riastradh in ticket #1881):

        lib/libc/time/strptime.c: revision 1.64 (patch)
        lib/libc/time/strptime.c: revision 1.65 (patch)
        lib/libc/time/strptime.c: revision 1.66 (patch)
        tests/lib/libc/time/t_strptime.c: revision 1.16 (patch)

strptime(3): Exercise some edge cases in the automatic tests.

Unfortunately, we can't quite use strptime as a black box to detect
the cases that triggered undefined behaviour, because strptime just
fails in that case anyway since the number that would go in .tm_year
is far out of the representable range.
PR lib/58041

strptime(3): Avoid arithmetic overflow.
PR lib/58041

strptime(3): Reduce unnecessary indentation.
Post-fix tidying.
No functional change intended.
PR lib/58041

strptime(3): Declare digit d as time_t.

This doesn't make a semantic difference -- d can only take on the ten
values {0,1,2,3,4,5,6,7,8,9}, and the arithmetic with it later all
comes out the same whether the type is unsigned or time_t, even if
time_t were int32_t instead of int64_t.

But it pacifies overzealous compilers used by downstream users of
this code.  And while it's silly to use a much wider type (64-bit
signed) than is needed here to store a single digit, it doesn't
really hurt either (32-bit unsigned is much larger than needed too).

PR lib/58041


To generate a diff of this commit:
cvs rdiff -u -r1.62 -r1.62.6.1 src/lib/libc/time/strptime.c
cvs rdiff -u -r1.15 -r1.15.4.1 src/tests/lib/libc/time/t_strptime.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/lib/libc/time/strptime.c
diff -u src/lib/libc/time/strptime.c:1.62 src/lib/libc/time/strptime.c:1.62.6.1
--- src/lib/libc/time/strptime.c:1.62	Thu Aug 24 01:01:09 2017
+++ src/lib/libc/time/strptime.c	Sat Aug 24 16:15:40 2024
@@ -1,4 +1,4 @@
-/*	$NetBSD: strptime.c,v 1.62 2017/08/24 01:01:09 ginsbach Exp $	*/
+/*	$NetBSD: strptime.c,v 1.62.6.1 2024/08/24 16:15:40 martin Exp $	*/
 
 /*-
  * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc.
@@ -31,7 +31,7 @@
 
 #include <sys/cdefs.h>
 #if defined(LIBC_SCCS) && !defined(lint)
-__RCSID("$NetBSD: strptime.c,v 1.62 2017/08/24 01:01:09 ginsbach Exp $");
+__RCSID("$NetBSD: strptime.c,v 1.62.6.1 2024/08/24 16:15:40 martin Exp $");
 #endif
 
 #include "namespace.h"
@@ -346,38 +346,39 @@ literal:
 			LEGAL_ALT(ALT_O);
 			continue;
 
-#ifndef TIME_MAX
-#define TIME_MAX	INT64_MAX
-#endif
-		case 's':	/* seconds since the epoch */
-			{
-				time_t sse = 0;
-				uint64_t rulim = TIME_MAX;
-
-				if (*bp < '0' || *bp > '9') {
-					bp = NULL;
-					continue;
-				}
+		case 's': {	/* seconds since the epoch */
+			const time_t TIME_MAX = __type_max(time_t);
+			time_t sse, d;
 
-				do {
-					sse *= 10;
-					sse += *bp++ - '0';
-					rulim /= 10;
-				} while ((sse * 10 <= TIME_MAX) &&
-					 rulim && *bp >= '0' && *bp <= '9');
+			if (*bp < '0' || *bp > '9') {
+				bp = NULL;
+				continue;
+			}
 
-				if (sse < 0 || (uint64_t)sse > TIME_MAX) {
+			sse = *bp++ - '0';
+			while (*bp >= '0' && *bp <= '9') {
+				d = *bp++ - '0';
+				if (sse > TIME_MAX/10) {
 					bp = NULL;
-					continue;
+					break;
 				}
-
-				if (localtime_r(&sse, tm) == NULL)
+				sse *= 10;
+				if (sse > TIME_MAX - d) {
 					bp = NULL;
-				else
-					state |= S_YDAY | S_WDAY |
-					    S_MON | S_MDAY | S_YEAR;
+					break;
+				}
+				sse += d;
 			}
+			if (bp == NULL)
+				continue;
+
+			if (localtime_r(&sse, tm) == NULL)
+				bp = NULL;
+			else
+				state |= S_YDAY | S_WDAY |
+				    S_MON | S_MDAY | S_YEAR;
 			continue;
+		}
 
 		case 'U':	/* The week of year, beginning on sunday. */
 		case 'W':	/* The week of year, beginning on monday. */

Index: src/tests/lib/libc/time/t_strptime.c
diff -u src/tests/lib/libc/time/t_strptime.c:1.15 src/tests/lib/libc/time/t_strptime.c:1.15.4.1
--- src/tests/lib/libc/time/t_strptime.c:1.15	Sun Jun  3 08:48:37 2018
+++ src/tests/lib/libc/time/t_strptime.c	Sat Aug 24 16:15:40 2024
@@ -1,4 +1,4 @@
-/* $NetBSD: t_strptime.c,v 1.15 2018/06/03 08:48:37 maya Exp $ */
+/* $NetBSD: t_strptime.c,v 1.15.4.1 2024/08/24 16:15:40 martin Exp $ */
 
 /*-
  * Copyright (c) 1998, 2008 The NetBSD Foundation, Inc.
@@ -32,11 +32,13 @@
 #include <sys/cdefs.h>
 __COPYRIGHT("@(#) Copyright (c) 2008\
  The NetBSD Foundation, inc. All rights reserved.");
-__RCSID("$NetBSD: t_strptime.c,v 1.15 2018/06/03 08:48:37 maya Exp $");
+__RCSID("$NetBSD: t_strptime.c,v 1.15.4.1 2024/08/24 16:15:40 martin Exp $");
 
-#include <time.h>
-#include <stdlib.h>
+#include <errno.h>
+#include <inttypes.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
 
 #include <atf-c.h>
 
@@ -441,6 +443,153 @@ ATF_TC_BODY(Zone, tc)
 	ztest("%Z");
 }
 
+ATF_TC(posixtime_overflow);
+
+ATF_TC_HEAD(posixtime_overflow, tc)
+{
+
+	atf_tc_set_md_var(tc, "descr",
+	    "Checks strptime(3) safely rejects POSIX time overfow");
+}
+
+ATF_TC_BODY(posixtime_overflow, tc)
+{
+	static const uint64_t P[] = { /* cases that should pass round-trip */
+		[0] = 0,
+		[1] = 1,
+		[2] = 2,
+		[3] = 0x7ffffffe,
+		[4] = 0x7fffffff,
+		[5] = 0x80000000,
+		[6] = 0x80000001,
+		[7] = 0xfffffffe,
+		[8] = 0xffffffff,
+		[9] = 0x100000000,
+		[10] = 0x100000001,
+		[11] = 67767976233532799, /* 2147483647-12-31T23:59:59 */
+		/*
+		 * Beyond this point, the year (.tm_year + 1900)
+		 * overflows the signed 32-bit range, so we won't be
+		 * able to test round-trips:
+		 */
+#if 0	/* localtime can't handle these years in 9.x */
+		[12] = 67767976233532800,
+		[13] = 67767976233532801,
+		[14] = 67768036191676799,
+#endif
+		/*
+		 * Beyond this point, .tm_year itself overflows the
+		 * signed 32-bit range, so strptime won't work at all;
+		 * the output can't be represented in struct tm.
+		 */
+#if 0
+		[15] = 67768036191676800,
+		[16] = 67768036191676801,
+		[17] = 0x7ffffffffffffffe,
+		[18] = 0x7fffffffffffffff,
+#endif
+	};
+	static const uint64_t F[] = { /* cases strptime should reject */
+		[0] = 67768036191676800,
+		[1] = 67768036191676801,
+		[2] = 0x7ffffffffffffffe,
+		[3] = 0x7fffffffffffffff,
+		[4] = 0x8000000000000000,
+		[5] = 0x8000000000000001,
+		[6] = 0xfffffffffffffffe,
+		[7] = 0xffffffffffffffff,
+	};
+	size_t i;
+
+	/*
+	 * Verify time_t fits in uint64_t, with space to spare since
+	 * it's signed.
+	 */
+	__CTASSERT(__type_max(time_t) < __type_max(uint64_t));
+
+	/*
+	 * Make sure we work in UTC so this test doesn't depend on
+	 * which time zone your machine is configured for.
+	 */
+	setenv("TZ", "UTC", 1);
+
+	/*
+	 * Check the should-pass cases.
+	 */
+	for (i = 0; i < __arraycount(P); i++) {
+		char buf[sizeof("18446744073709551616")];
+		int n;
+		struct tm tm;
+		time_t t;
+		int error;
+
+		/*
+		 * Format the integer in decimal.
+		 */
+		n = snprintf(buf, sizeof(buf), "%"PRIu64, P[i]);
+		ATF_CHECK_MSG(n >= 0 && (unsigned)n < sizeof(buf),
+		    "P[%zu]: 64-bit requires %d digits", i, n);
+
+		/*
+		 * Parse the time into components.
+		 */
+		fprintf(stderr, "# P[%zu]: %"PRId64"\n", i, P[i]);
+		if (strptime(buf, "%s", &tm) == NULL) {
+			atf_tc_fail_nonfatal("P[%zu]: strptime failed", i);
+			continue;
+		}
+		fprintf(stderr, "tm_sec=%d\n", tm.tm_sec);
+		fprintf(stderr, "tm_min=%d\n", tm.tm_min);
+		fprintf(stderr, "tm_hour=%d\n", tm.tm_hour);
+		fprintf(stderr, "tm_mday=%d\n", tm.tm_mday);
+		fprintf(stderr, "tm_mon=%d\n", tm.tm_mon);
+		fprintf(stderr, "tm_year=%d\n", tm.tm_year);
+		fprintf(stderr, "tm_wday=%d\n", tm.tm_wday);
+		fprintf(stderr, "tm_yday=%d\n", tm.tm_yday);
+		fprintf(stderr, "tm_isdst=%d\n", tm.tm_isdst);
+		fprintf(stderr, "tm_gmtoff=%ld\n", tm.tm_gmtoff);
+		fprintf(stderr, "tm_zone=%s\n", tm.tm_zone);
+
+		/*
+		 * Convert back to POSIX seconds since epoch -- unless
+		 * the year number overflows signed 32-bit, in which
+		 * case stop here because we can't test further.
+		 */
+		if (tm.tm_year > 0x7fffffff - 1900)
+			continue;
+		t = mktime(&tm);
+		error = errno;
+		ATF_CHECK_MSG(t != -1, "P[%zu]: mktime failed: %d, %s",
+		    i, error, strerror(error));
+
+		/*
+		 * Verify the round-trip.
+		 */
+		ATF_CHECK_EQ_MSG(P[i], (uint64_t)t,
+		    "P[%zu]: %"PRId64" -> %"PRId64, i, P[i], (int64_t)t);
+	}
+
+	/*
+	 * Check the should-fail cases.
+	 */
+	for (i = 0; i < __arraycount(F); i++) {
+		char buf[sizeof("18446744073709551616")];
+		int n;
+
+		/*
+		 * Format the integer in decimal.
+		 */
+		n = snprintf(buf, sizeof(buf), "%"PRIu64, F[i]);
+		ATF_CHECK_MSG(n >= 0 && (unsigned)n < sizeof(buf),
+		    "F[%zu]: 64-bit requires %d digits", i, n);
+
+		/*
+		 * Verify strptime rejects this.
+		 */
+		h_fail(buf, "%s");
+	}
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 
@@ -452,6 +601,7 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, year);
 	ATF_TP_ADD_TC(tp, zone);
 	ATF_TP_ADD_TC(tp, Zone);
+	ATF_TP_ADD_TC(tp, posixtime_overflow);
 
 	return atf_no_error();
 }

Reply via email to