fix(esp_event): prevent UAF race between post and loop delete (SEC-222)

esp_event_post_to() could access loop->queue / loop->mutex after
esp_event_loop_delete() freed them when both ran concurrently.

Introduce esp_event_loop_state_t with:
- posts_in_flight: reference-count incremented atomically (under
  state.lock spinlock) before touching any loop resources, decremented
  on every exit path via goto on_err.
- deleting: atomic_bool set by esp_event_loop_delete() to block new
  posts from entering the critical section.

esp_event_loop_delete() sets deleting=true, then busy-waits (releasing
and re-acquiring loop->mutex each tick) until posts_in_flight reaches
zero before proceeding with teardown.

esp_event_isr_post_to() performs a lock-free atomic_load of deleting as
a best-effort guard; ISR context cannot participate in the spinlock
protocol but the window is documented and accepted.
This commit is contained in:
Konstantin Kondrashov
2026-07-21 17:16:07 +03:00
parent fca32e128a
commit 9947dfdc58
3 changed files with 62 additions and 5 deletions
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -66,6 +66,12 @@ typedef struct esp_event_loop_node {
typedef SLIST_HEAD(esp_event_loop_nodes, esp_event_loop_node) esp_event_loop_nodes_t;
typedef struct esp_event_loop_state {
portMUX_TYPE lock; /**< spinlock protecting deleting and posts_in_flight */
atomic_bool deleting; /**< true when loop deletion has started; read lock-free from ISR */
uint32_t posts_in_flight; /**< task-context posts that passed the post-entry gate */
} esp_event_loop_state_t;
/// Event loop
typedef struct esp_event_loop_instance {
const char* name; /**< name of this event loop */
@@ -76,6 +82,7 @@ typedef struct esp_event_loop_instance {
SemaphoreHandle_t mutex; /**< mutex for updating the events linked list */
esp_event_loop_nodes_t loop_nodes; /**< set of linked lists containing the
registered handlers for the loop */
esp_event_loop_state_t state; /**< loop deletion and post-entry state */
#ifdef CONFIG_ESP_EVENT_LOOP_PROFILING
atomic_uint_least32_t events_received; /**< number of events successfully posted to the loop */
atomic_uint_least32_t events_dropped; /**< number of events dropped due to queue being full */