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();
}