You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Defer freeing the dynamically allocated CMSIS TimerCallback_t until the
FreeRTOS timer daemon actually processes tmrCOMMAND_DELETE.
Problem observed in the field
This fixes a real failure that we encountered on an embedded product; it is
not only a theoretical race. We observed intermittent HardFaults/system
crashes in code using CMSIS-RTOS2 timers. The failures were most reproducible
in these situations:
stopping and deleting a timer close to its expiry time;
creating a timer and deleting it immediately; and
deleting timers while the timer daemon command queue had a backlog.
Instrumentation of the timer daemon and heap showed the following sequence:
osTimerDelete() calls xTimerDelete().
xTimerDelete() returns pdPASS after placing tmrCOMMAND_DELETE in the
timer command queue. The timer has not necessarily been deleted yet.
The current osTimerDelete() immediately frees the dynamically allocated TimerCallback_t referenced by pvTimerID.
Before the daemon handles the delete command, the timer can expire and TimerCallback() dereferences the already freed callback context.
Depending on whether the freed block had already been reused, this produced a
use-after-free, an invalid function/argument access, and eventually a
HardFault or system hang.
Root cause
FreeRTOS software timer commands are asynchronous. A successful return from xTimerDelete() means that the delete command was queued; it does not mean
that the timer daemon has processed it. The callback context therefore has to
remain alive until tmrCOMMAND_DELETE is handled.
Fix
Add a timer-delete completion callback invoked by the timer daemon while the
timer control block, callback function, and pvTimerID are still valid.
Register a CMSIS cleanup callback during osKernelInitialize().
Release TimerCallback_t only when the deleted timer uses the CMSIS TimerCallback trampoline and its pvTimerID carries the dynamic-allocation
flag.
Preserve the existing heap_1 behavior, and do not free callback contexts
belonging to native FreeRTOS timers or statically allocated CMSIS timers.
This avoids using a second queued cleanup command. Such an approach is not
atomic with xTimerDelete() and can fail when the timer queue has only one
free slot.
Validation
Built the repository's FreeRTOS Kernel CMake example successfully with GCC,
including the modified timers.c.
On the affected product, daemon instrumentation confirmed that callback
context is retained until delete processing completes.
Verified normal system startup and a timer-heavy weather application after
applying the fix.
Hardware A/B validation on STM32F407ZG (Cortex-M4F)
We built a dedicated on-target validation firmware to prove both (a) the bug is real and deterministic under the old behavior, and (b) this fix removes it. All numbers below are from a real board over UART.
Phase A - old osTimerDelete() behavior (immediate free of callback context)
The firmware reproduces the unsafe free exactly as the old wrapper does: start the timer, let the daemon arm it, then free and immediately reuse (poison) the TimerCallback_t while the timer is still armed and can still expire.
uaf stays frozen at the 32 baseline count: no new use-after-free in 8300+ stress loops.
live_cb == delete_cb (both 8300): the callback context stays alive until the daemon actually processes tmrCOMMAND_DELETE, then is freed exactly once.
native_skip=32 with unexpected=0: native FreeRTOS timer pvTimerID values are never freed by the CMSIS cleanup path.
heap flat at 61568 bytes over 8300+ loops, min_heap stable: no leak, no double free, no heap corruption.
No HardFault, no assert, no stack overflow across the whole run.
Bottom line
The old wrapper's use-after-free is deterministically reproducible on hardware (32/32), and the fixed daemon-delete-callback path shows zero UAF across a long stress run with exact once-only freeing, native-timer safety, and stable heap. This is a real product failure (intermittent HardFaults/crashes), not a theoretical race.
The corresponding FreeRTOS-Kernel change has now been submitted as FreeRTOS/FreeRTOS-Kernel#1469. This CMSIS wrapper fix depends on the timer-delete completion callback introduced there.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Defer freeing the dynamically allocated CMSIS
TimerCallback_tuntil theFreeRTOS timer daemon actually processes
tmrCOMMAND_DELETE.Problem observed in the field
This fixes a real failure that we encountered on an embedded product; it is
not only a theoretical race. We observed intermittent HardFaults/system
crashes in code using CMSIS-RTOS2 timers. The failures were most reproducible
in these situations:
Instrumentation of the timer daemon and heap showed the following sequence:
osTimerDelete()callsxTimerDelete().xTimerDelete()returnspdPASSafter placingtmrCOMMAND_DELETEin thetimer command queue. The timer has not necessarily been deleted yet.
osTimerDelete()immediately frees the dynamically allocatedTimerCallback_treferenced bypvTimerID.TimerCallback()dereferences the already freed callback context.Depending on whether the freed block had already been reused, this produced a
use-after-free, an invalid function/argument access, and eventually a
HardFault or system hang.
Root cause
FreeRTOS software timer commands are asynchronous. A successful return from
xTimerDelete()means that the delete command was queued; it does not meanthat the timer daemon has processed it. The callback context therefore has to
remain alive until
tmrCOMMAND_DELETEis handled.Fix
timer control block, callback function, and
pvTimerIDare still valid.osKernelInitialize().TimerCallback_tonly when the deleted timer uses the CMSISTimerCallbacktrampoline and itspvTimerIDcarries the dynamic-allocationflag.
belonging to native FreeRTOS timers or statically allocated CMSIS timers.
This avoids using a second queued cleanup command. Such an approach is not
atomic with
xTimerDelete()and can fail when the timer queue has only onefree slot.
Validation
including the modified
timers.c.context is retained until delete processing completes.
applying the fix.