From 8e8627f4f17e30cd00d831a4af95e010d2b33ce4 Mon Sep 17 00:00:00 2001 From: Thomas Heller Date: Thu, 8 Jan 2026 09:31:50 +0100 Subject: [PATCH 1/2] Fix: stack-use-after-free after recent run_loop changes The changes introduced to #1683 (changing run_loop from a condition variable to atomics) introduced a stack-use-after-free when the task completion is happening on a different thread: `run_loop::finish` might cause `sync_wait_t::apply_sender` to return before scheduling the noop task, ending the lifetime of the queue before the noop task push could be finalized: - Thread 1 sets __finishing_ to true - Thread 2 observed __finishing is true, __noop_task hasn't been pushed yet and finishes successfully returns. - Thread 1 tries to push __noop_task to the queue, which now is out of scope. To avoid this situation, the sync_wait state is additionally synchronized on the `finish` being returning successfully (avoiding the aforementioned race condition). --- include/stdexec/__detail/__sync_wait.hpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/include/stdexec/__detail/__sync_wait.hpp b/include/stdexec/__detail/__sync_wait.hpp index e36918bd3..7c9ed2762 100644 --- a/include/stdexec/__detail/__sync_wait.hpp +++ b/include/stdexec/__detail/__sync_wait.hpp @@ -32,6 +32,8 @@ #include "__run_loop.hpp" #include "__type_traits.hpp" +#include "__atomic.hpp" + #include #include #include @@ -89,6 +91,20 @@ namespace stdexec { struct __state { std::exception_ptr __eptr_; run_loop __loop_; + stdexec::__std::atomic __done_{false}; + + void finish() noexcept { + __loop_.finish(); + __done_.store(true, stdexec::__std::memory_order_release); + __done_.notify_all(); + } + + void wait() noexcept { + // Account for spurios wakeups + while (!__done_.load(stdexec::__std::memory_order_acquire)) { + __done_.wait(false, stdexec::__std::memory_order_acquire); + } + } }; template @@ -108,7 +124,7 @@ namespace stdexec { STDEXEC_CATCH_ALL { __state_->__eptr_ = std::current_exception(); } - __state_->__loop_.finish(); + __state_->finish(); } template @@ -121,11 +137,11 @@ namespace stdexec { } else { __state_->__eptr_ = std::make_exception_ptr(static_cast<_Error&&>(__err)); } - __state_->__loop_.finish(); + __state_->finish(); } void set_stopped() noexcept { - __state_->__loop_.finish(); + __state_->finish(); } [[nodiscard]] @@ -286,6 +302,7 @@ namespace stdexec { // Wait for the variant to be filled in. __local_state.__loop_.run(); + __local_state.wait(); if (__local_state.__eptr_) { std::rethrow_exception(static_cast(__local_state.__eptr_)); From d71107457a86ca29a9b278e6eee7abeedea7e0af Mon Sep 17 00:00:00 2001 From: Thomas Heller Date: Fri, 9 Jan 2026 14:05:32 +0100 Subject: [PATCH 2/2] avoid unnecessary looping --- include/stdexec/__detail/__sync_wait.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/include/stdexec/__detail/__sync_wait.hpp b/include/stdexec/__detail/__sync_wait.hpp index 7c9ed2762..75b05ced7 100644 --- a/include/stdexec/__detail/__sync_wait.hpp +++ b/include/stdexec/__detail/__sync_wait.hpp @@ -100,10 +100,7 @@ namespace stdexec { } void wait() noexcept { - // Account for spurios wakeups - while (!__done_.load(stdexec::__std::memory_order_acquire)) { - __done_.wait(false, stdexec::__std::memory_order_acquire); - } + __done_.wait(false, stdexec::__std::memory_order_acquire); } };