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 15:25:58 +03:00
parent 736275e562
commit 9d2d32524b
3 changed files with 64 additions and 6 deletions
@@ -17,6 +17,7 @@
extern "C" {
#include "Mocktask.h"
#include "Mockqueue.h"
#include "Mockportmacro.h"
}
namespace {
@@ -102,6 +103,8 @@ TEST_CASE("test esp_event_loop_create no_task(void)")
xQueueTakeMutexRecursive_IgnoreAndReturn(0);
xQueueGiveMutexRecursive_IgnoreAndReturn(0);
xQueueReceive_IgnoreAndReturn(0);
vPortEnterCritical_Ignore();
vPortExitCritical_Ignore();
esp_event_loop_handle_t loop = nullptr;
esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
@@ -115,6 +118,8 @@ TEST_CASE("test esp_event_loop_create no_task(void)")
xQueueReceive_StopIgnore();
xQueueTakeMutexRecursive_StopIgnore();
xQueueGiveMutexRecursive_StopIgnore();
vPortEnterCritical_StopIgnore();
vPortExitCritical_StopIgnore();
}
TEST_CASE("test esp_event_loop_create with_task(void)")
@@ -125,6 +130,8 @@ TEST_CASE("test esp_event_loop_create with_task(void)")
xQueueTakeMutexRecursive_IgnoreAndReturn(0);
xQueueGiveMutexRecursive_IgnoreAndReturn(0);
xQueueReceive_IgnoreAndReturn(0);
vPortEnterCritical_Ignore();
vPortExitCritical_Ignore();
esp_event_loop_handle_t loop = nullptr;
esp_event_loop_args_t loop_args = test_event_get_default_loop_args();
@@ -138,6 +145,8 @@ TEST_CASE("test esp_event_loop_create with_task(void)")
xQueueReceive_StopIgnore();
xQueueTakeMutexRecursive_StopIgnore();
xQueueGiveMutexRecursive_StopIgnore();
vPortEnterCritical_StopIgnore();
vPortExitCritical_StopIgnore();
}
TEST_CASE("registering with ANY_BASE but specific ID fails")