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.