From bdf98cf0ddadc02fbd413be4992353e5a1d60070 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Mon, 7 Sep 2026 09:34:03 +0200 Subject: [PATCH] fix(esp_libc): reject out-of-range adjtime() deltas instead of overflowing The adjtime() wrapper stored the microsecond offset into the 32-bit `long` timex.offset field without checking for overflow. A large delta (e.g. 400 days) was computed in 64-bit and silently truncated when assigned, wrapping into a small value that passed the ~35 minute range check. adjtime() then returned 0 instead of the expected -1. Compute the offset in int64_t and reject values that do not fit in the timex.offset field with EINVAL. Add a regression test for a multi-day delta. Closes https://github.com/espressif/esp-idf/issues/19051 --- components/esp_libc/src/time.c | 9 ++++++++- .../esp_libc/test_apps/newlib/main/test_time.c | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/components/esp_libc/src/time.c b/components/esp_libc/src/time.c index 991d58dc550..17513b1692f 100644 --- a/components/esp_libc/src/time.c +++ b/components/esp_libc/src/time.c @@ -189,8 +189,15 @@ WEAK_UNLESS_TIMEFUNC_IMPL int adjtime(const struct timeval *delta, struct timeva struct timex tx = {0}; if (delta != NULL) { + // Reject deltas that do not fit in the 32-bit struct timex.offset (µs) field, + // otherwise the value would be silently truncated and pass the range check. + int64_t offset_us = (int64_t)delta->tv_sec * 1000000LL + delta->tv_usec; + if (offset_us > LONG_MAX || offset_us < LONG_MIN) { + errno = EINVAL; + return -1; + } tx.modes = ADJ_OFFSET_SINGLESHOT; - tx.offset = delta->tv_sec * 1000000L + delta->tv_usec; + tx.offset = (long)offset_us; } else { tx.modes = ADJ_OFFSET_SS_READ; } diff --git a/components/esp_libc/test_apps/newlib/main/test_time.c b/components/esp_libc/test_apps/newlib/main/test_time.c index 5051ed2a6ef..6a94a2ab461 100644 --- a/components/esp_libc/test_apps/newlib/main/test_time.c +++ b/components/esp_libc/test_apps/newlib/main/test_time.c @@ -192,6 +192,18 @@ void test_adjtime_function(test_adjtime_mode_t mode, test_clock_adjtime_units_t TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, &tv_outdelta, mode, units), -1); } + // a multi-day delta must be rejected, not silently + // truncated into the 32-bit timex.offset (µs) field and applied as a small slew. + if (mode == TEST_ADJTIME_MODE_LEGACY) { + tv_delta.tv_sec = 400L * 24 * 60 * 60; // 400 days + tv_delta.tv_usec = 123456; + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, NULL, mode, units), -1); + + tv_delta.tv_sec = -400L * 24 * 60 * 60; + tv_delta.tv_usec = -123456; + TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, NULL, mode, units), -1); + } + tv_delta.tv_sec = 0; tv_delta.tv_usec = -900000; TEST_ASSERT_EQUAL(realtime_adjtime_wrapper(&tv_delta, &tv_outdelta, mode, units), 0);