Module Name:    src
Committed By:   riastradh
Date:           Sat Mar 16 00:06:37 UTC 2024

Modified Files:
        src/tests/lib/libc/time: t_strptime.c

Log Message:
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


To generate a diff of this commit:
cvs rdiff -u -r1.15 -r1.16 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/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.16
--- 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 Mar 16 00:06:37 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.16 2024/03/16 00:06:37 riastradh 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.16 2024/03/16 00:06:37 riastradh 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,151 @@ 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:
+		 */
+		[12] = 67767976233532800,
+		[13] = 67767976233532801,
+		[14] = 67768036191676799,
+		/*
+		 * 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 +599,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