From dae815728f73c21028fcd73f5ac2a46b08ded317 Mon Sep 17 00:00:00 2001 From: 94xhn <87560781+94xhn@users.noreply.github.com> Date: Sun, 12 Jul 2026 00:21:13 +0800 Subject: [PATCH] fix(IWatchdog): guard reload/window register computation against underflow IWatchdogClass::set() computes the IWDG reload register with reload = (uint32_t)(t_sec / div) - 1; (and the equivalent for the optional 'window' register), with no check that (uint32_t)(t_sec / div) is non-zero before subtracting 1. IWDG_TIMEOUT_MIN, the documented and enforced lower bound for the 'timeout' argument, is defined as (4*1000000)/LSI_VALUE using integer division. On most series (LSI_VALUE 32000 or 40000) this divides evenly, so calling begin(IWDG_TIMEOUT_MIN) is always safe. On STM32L0xx/STM32L1xx (LSI_VALUE = 37000), 4000000/37000 is not an integer (108.108...), so the macro truncates down to 108 -- a value IS_IWDG_TIMEOUT() accepts, but that is below the true precision needed: t_sec works out to 3.996 there, so (uint32_t)(t_sec / 4) floors to 0, and "0 - 1" underflows reload to 0xFFFFFFFF. LL_IWDG_SetReloadCounter() then masks that into the 12-bit RLR register as 0x0FFF (4095), the maximum possible value, instead of 0. Net effect: on STM32L0/L1, IWatchdog.begin(IWDG_TIMEOUT_MIN) -- the shortest legal, documented watchdog timeout -- silently programs a timeout of about 443 ms instead of about 108 us (roughly 4100x longer), with no error indication. The same underflow shape is reachable through the optional 'window' parameter for the same reason. Guard both computations with an explicit "ticks > 0" check before subtracting, returning 0 (the safe minimum reload) instead of underflowing, without changing the public API or behavior on any series where the computation was already safe. Signed-off-by: 94xhn <87560781+94xhn@users.noreply.github.com> --- libraries/IWatchdog/src/IWatchdog.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/libraries/IWatchdog/src/IWatchdog.cpp b/libraries/IWatchdog/src/IWatchdog.cpp index 27246fbfa5..af2ede57b1 100644 --- a/libraries/IWatchdog/src/IWatchdog.cpp +++ b/libraries/IWatchdog/src/IWatchdog.cpp @@ -94,7 +94,17 @@ void IWatchdogClass::set(uint32_t timeout, uint32_t window) if (--prescaler > LL_IWDG_PRESCALER_256) { return; } - reload = (uint32_t)(t_sec / div) - 1; + { + uint32_t ticks = (uint32_t)(t_sec / div); + // 'ticks' can floor to 0 right at the documented minimum timeout on + // series whose LSI_VALUE does not divide IWDG_TIMEOUT_MIN evenly + // (e.g. STM32L0xx/L1xx, LSI_VALUE=37000): guard against the unsigned + // underflow that would otherwise wrap 'reload' to 0xFFFFFFFF and, + // once masked into the 12-bit RLR register, silently program the + // watchdog for close to its longest possible timeout instead of the + // shortest one that was actually requested. + reload = (ticks > 0) ? (ticks - 1) : 0; + } // Enable register access by writing 0x0000 5555 in the IWDG_KR register LL_IWDG_EnableWriteAccess(IWDG); @@ -111,7 +121,9 @@ void IWatchdogClass::set(uint32_t timeout, uint32_t window) // Reset window value reload = IWDG_WINR_WIN; } else { - reload = (uint32_t)(((float)window / 1000000 * LSI_VALUE) / div) - 1; + // Same underflow risk as the 'timeout' -> reload conversion above. + uint32_t window_ticks = (uint32_t)(((float)window / 1000000 * LSI_VALUE) / div); + reload = (window_ticks > 0) ? (window_ticks - 1) : 0; } LL_IWDG_SetWindow(IWDG, reload); }