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