Commit 565072f07b for strongswan.org

commit 565072f07b814fcb6c6192a0fd230f42ef58ccb3
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Fri Sep 18 14:32:34 2026 +0200

    time: Add helper to parse a time span to an unsigned 64-bit value

    Also improves the existing parser by adding overflow checks and support
    for uppercase unit suffixes.  The parser now also rejects strings that
    don't end after the value/suffix, while still allowing whitespace at the
    start and between value and suffix.

diff --git a/src/libstrongswan/tests/suites/test_utils.c b/src/libstrongswan/tests/suites/test_utils.c
index 7da9dc8b76..9c99b864f4 100644
--- a/src/libstrongswan/tests/suites/test_utils.c
+++ b/src/libstrongswan/tests/suites/test_utils.c
@@ -348,43 +348,127 @@ START_TEST(test_base_from_string)
 END_TEST

 /*******************************************************************************
- * timespan_from_string
+ * [uint64_]timespan_from_string
  */

 static struct {
 	char *s;
 	char *u;
 	bool v;
-	time_t t;
+	uint64_t t;
+} ts64_data[] = {
+	{NULL,	NULL,	FALSE,	0},
+	{"",	NULL,	FALSE,	0},
+	{" ",	NULL,	FALSE,	0},
+	{"a",	NULL,	FALSE,	0},
+	{" a",	NULL,	FALSE,	0},
+	{"0",	NULL,	TRUE,	0},
+	{"5",	NULL,	TRUE,	5},
+	{"5 ",	NULL,	FALSE,	0},
+	{" 5",	NULL,	TRUE,	5},
+	{"-5",	NULL,	FALSE,	0},
+	{"0x5",	NULL,	FALSE,	0},
+	{"5s",	NULL,	TRUE,	5},
+	{"5 s",	NULL,	TRUE,	5},
+	{"5s ",	NULL,	FALSE,	0},
+	{"5S",	NULL,	TRUE,	5},
+	{"5m",	NULL,	TRUE,	300},
+	{"5M",	NULL,	TRUE,	300},
+	{"5ms",	NULL,	FALSE,	0},
+	{"5h",	NULL,	TRUE,	18000},
+	{"5H",	NULL,	TRUE,	18000},
+	{"5d",	NULL,	TRUE,	432000},
+	{"5D",	NULL,	TRUE,	432000},
+	{"5x",	NULL,	FALSE,	0},
+	{"5 x",	NULL,	FALSE,	0},
+	{"5",	"",		TRUE,	5},
+	{"5",	"m",	TRUE,	300},
+	{"5",	"ms",	TRUE,	300},
+	{"5",	"x",	FALSE,	0},
+	{"5x",	"m",	FALSE,	0},
+	{"213503982334601d",		NULL,	TRUE,	18446744073709526400ULL},
+	{"213503982334602d",		NULL,	FALSE,	0},
+	{"18446744073709551615",	NULL,	TRUE,	UINT64_MAX},
+	{"18446744073709551615s",	NULL,	TRUE,	UINT64_MAX},
+	{"18446744073709551616",	NULL,	FALSE,	0},
+};
+
+START_TEST(test_uint64_timespan_from_string)
+{
+	uint64_t val = 42;
+
+	ck_assert(uint64_timespan_from_string(ts64_data[_i].s, ts64_data[_i].u,
+										  NULL) == ts64_data[_i].v);
+	ck_assert(uint64_timespan_from_string(ts64_data[_i].s, ts64_data[_i].u,
+										  &val) == ts64_data[_i].v);
+	if (ts64_data[_i].v)
+	{
+		ck_assert_int_eq(val, ts64_data[_i].t);
+	}
+	else
+	{
+		ck_assert_int_eq(val, 42);
+	}
+}
+END_TEST
+
+static struct {
+	char *s;
+	char *u;
+	bool v;
+	/* don't use time_t in case it's only 32-bit */
+	uint64_t t;
 } ts_data[] = {
 	{NULL,	NULL,	FALSE,	0},
 	{"",	NULL,	FALSE,	0},
+	{" ",	NULL,	FALSE,	0},
 	{"a",	NULL,	FALSE,	0},
+	{" a",	NULL,	FALSE,	0},
 	{"0",	NULL,	TRUE,	0},
 	{"5",	NULL,	TRUE,	5},
+	{"5 ",	NULL,	FALSE,	0},
+	{" 5",	NULL,	TRUE,	5},
+	{"-5",	NULL,	FALSE,	0},
+	{"0x5",	NULL,	FALSE,	0},
 	{"5s",	NULL,	TRUE,	5},
+	{"5 s",	NULL,	TRUE,	5},
+	{"5s ",	NULL,	FALSE,	0},
+	{"5S",	NULL,	TRUE,	5},
 	{"5m",	NULL,	TRUE,	300},
-	{"5ms",	NULL,	TRUE,	300},
+	{"5M",	NULL,	TRUE,	300},
+	{"5ms",	NULL,	FALSE,	0},
 	{"5h",	NULL,	TRUE,	18000},
+	{"5H",	NULL,	TRUE,	18000},
 	{"5d",	NULL,	TRUE,	432000},
+	{"5D",	NULL,	TRUE,	432000},
 	{"5x",	NULL,	FALSE,	0},
+	{"5 x",	NULL,	FALSE,	0},
 	{"5",	"",		TRUE,	5},
 	{"5",	"m",	TRUE,	300},
 	{"5",	"ms",	TRUE,	300},
 	{"5",	"x",	FALSE,	0},
 	{"5x",	"m",	FALSE,	0},
+	{"2147483647",				NULL,	TRUE,	INT32_MAX},
+	{"9223372036854775807",		NULL,	TRUE,	INT64_MAX},
+	{"9223372036854775808",		NULL,	FALSE,	0},
 	{"18446744073709551616",	NULL,	FALSE,	0},
 };

 START_TEST(test_timespan_from_string)
 {
 	time_t val = 42;
+	bool exp = ts_data[_i].v;
+
+	if (sizeof(time_t) == 4 && ts_data[_i].t > INT32_MAX)
+	{
+		exp = FALSE;
+	}

 	ck_assert(timespan_from_string(ts_data[_i].s, ts_data[_i].u,
-								   NULL) == ts_data[_i].v);
+								   NULL) == exp);
 	ck_assert(timespan_from_string(ts_data[_i].s, ts_data[_i].u,
-								   &val) == ts_data[_i].v);
-	if (ts_data[_i].v)
+								   &val) == exp);
+	if (exp)
 	{
 		ck_assert_int_eq(val, ts_data[_i].t);
 	}
@@ -1575,7 +1659,8 @@ Suite *utils_suite_create()
 	tcase_add_test(tc, test_base_from_string);
 	suite_add_tcase(s, tc);

-	tc = tcase_create("timespan_from_string");
+	tc = tcase_create("[uint64_]timespan_from_string");
+	tcase_add_loop_test(tc, test_uint64_timespan_from_string, 0, countof(ts64_data));
 	tcase_add_loop_test(tc, test_timespan_from_string, 0, countof(ts_data));
 	suite_add_tcase(s, tc);

diff --git a/src/libstrongswan/utils/utils/time.c b/src/libstrongswan/utils/utils/time.c
index 657d19950c..0ae1887652 100644
--- a/src/libstrongswan/utils/utils/time.c
+++ b/src/libstrongswan/utils/utils/time.c
@@ -83,55 +83,87 @@ time_t time_monotonic(timeval_t *tv)
 /*
  * Described in header
  */
-bool timespan_from_string(char *str, char *defunit, time_t *val)
+bool uint64_timespan_from_string(const char *str, const char *defunit,
+								 uint64_t *val)
 {
-	char *endptr, unit;
-	time_t timeval;
+	char *endptr, *suffix, unit;
+	uint64_t timeval, mul = 1;

-	if (str)
+	if (!uint64_from_string(str, &endptr, 10, &timeval))
 	{
-		errno = 0;
-		timeval = strtoull(str, &endptr, 10);
-		if (endptr == str)
-		{
+		return FALSE;
+	}
+	suffix = endptr;
+	while (isspace((u_char)*suffix))
+	{
+		suffix++;
+	}
+	unit = *suffix;
+	if (unit)
+	{
+		if (*++suffix)
+		{	/* content after the suffix */
 			return FALSE;
 		}
-		if (errno == 0)
-		{
-			while (isspace(*endptr))
-			{
-				endptr++;
-			}
-			unit = *endptr;
-			if (!unit && defunit)
-			{
-				unit = *defunit;
-			}
-			switch (unit)
-			{
-				case 'd':		/* time in days */
-					timeval *= 24 * 3600;
-					break;
-				case 'h':		/* time in hours */
-					timeval *= 3600;
-					break;
-				case 'm':		/* time in minutes */
-					timeval *= 60;
-					break;
-				case 's':		/* time in seconds */
-				case '\0':
-					break;
-				default:
-					return FALSE;
-			}
-			if (val)
-			{
-				*val = timeval;
-			}
-			return TRUE;
-		}
 	}
-	return FALSE;
+	else if (suffix != endptr)
+	{	/* trailing whitespace without suffix */
+		return FALSE;
+	}
+	else if (defunit)
+	{
+		unit = *defunit;
+	}
+	switch (unit)
+	{
+		case 'd':
+		case 'D':	/* time in days */
+			mul = 24 * 3600;
+			break;
+		case 'h':
+		case 'H':	/* time in hours */
+			mul = 3600;
+			break;
+		case 'm':
+		case 'M':	/* time in minutes */
+			mul = 60;
+			break;
+		case 's':
+		case 'S':	/* time in seconds */
+			break;
+		case '\0':
+			break;
+		default:
+			return FALSE;
+	}
+	if (__builtin_mul_overflow(timeval, mul, &timeval))
+	{
+		return FALSE;
+	}
+	if (val)
+	{
+		*val = timeval;
+	}
+	return TRUE;
+}
+
+/*
+ * Described in header
+ */
+bool timespan_from_string(const char *str, const char *defunit, time_t *val)
+{
+	uint64_t timeval;
+
+	if (!uint64_timespan_from_string(str, defunit, &timeval) ||
+		timeval > INT64_MAX || (sizeof(time_t) == 4 && timeval > INT32_MAX))
+	{
+		return FALSE;
+	}
+	if (val)
+	{
+		*val = timeval;
+	}
+	return TRUE;
 }

 /*
diff --git a/src/libstrongswan/utils/utils/time.h b/src/libstrongswan/utils/utils/time.h
index 6b283ab9d1..471fcaf3c2 100644
--- a/src/libstrongswan/utils/utils/time.h
+++ b/src/libstrongswan/utils/utils/time.h
@@ -86,16 +86,35 @@ static inline void timeval_add_ms(timeval_t *tv, u_int ms)
 }

 /**
- * Parse the given string as time span and return the number of seconds,
- * optionally with a default unit ('s' for seconds, 'm' for minutes, 'h' for
- * hours, 'd' for days - default is 's').
+ * Parse the given string as time span, with optional unit suffix ('s' for
+ * seconds, 'm' for minutes, 'h' for hours, 'd' for days), and return the
+ * resulting number of seconds as an unsigned 64-bit integer.
  *
- * @param str		value to parse
- * @param defunit	optional default unit
- * @param[out] val	parsed value
- * @return			TRUE if a value was parsed
+ * The default unit may optionally be specified with \p defunit (same as the
+ * suffix, default is 's').
+ *
+ * @param str		value to parse (NULL-safe)
+ * @param defunit	default unit (optional, only the first character is used)
+ * @param[out] val	parsed value on success (optional)
+ * @return			TRUE if a value was successfully parsed and fits \c uint64_t
+ */
+bool uint64_timespan_from_string(const char *str, const char *defunit,
+								 uint64_t *val);
+
+/**
+ * Parse the given string as time span, with optional unit suffix ('s' for
+ * seconds, 'm' for minutes, 'h' for hours, 'd' for days), and return the
+ * resulting number of seconds as a \c time_t.
+ *
+ * The default unit may optionally be specified with \p defunit (same as the
+ * suffix, default is 's').
+ *
+ * @param str		value to parse (NULL-safe)
+ * @param defunit	default unit (optional, only the first character is used)
+ * @param[out] val	parsed value on success (optional)
+ * @return			TRUE if a value was successfully parsed and fits \c time_t
  */
-bool timespan_from_string(char *str, char *defunit, time_t *val);
+bool timespan_from_string(const char *str, const char *defunit, time_t *val);

 /**
  * printf hook for time_t.