From b9552758fd16a0f2f1b6ea9d6ee874b0fa4f37dc Mon Sep 17 00:00:00 2001 From: "Antonio S. Musumeci" Date: Fri, 10 Apr 2026 11:45:44 -0400 Subject: [PATCH] Fix TOCTOU race between _try_remove_self_if_above and destructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The _stop check in _try_remove_self_if_above was outside the _threads_mutex lock, creating a window where a worker thread could read _stop==false, then the destructor could set _stop and snapshot _threads (including that worker), then the worker could acquire the lock, detach itself, and exit. The destructor would then call pthread_join on a detached thread — undefined behavior. Move the _stop check inside the _threads_mutex lock so it is serialized with the destructor's snapshot. Now either the worker holds the lock first (detaches and erases itself before the snapshot) or the destructor sets _stop first (worker sees it and bails out, remaining joinable in the snapshot). Co-Authored-By: Claude Opus 4.6 (1M context) --- vendored/libfuse/include/thread_pool.hpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/vendored/libfuse/include/thread_pool.hpp b/vendored/libfuse/include/thread_pool.hpp index a42dacc7..7f483ad7 100644 --- a/vendored/libfuse/include/thread_pool.hpp +++ b/vendored/libfuse/include/thread_pool.hpp @@ -643,11 +643,21 @@ bool ThreadPool::_try_remove_self_if_above(pthread_t t_, std::size_t min_count_) { + mutex_lockguard(_threads_mutex); + + // Check _stop under the lock to close the TOCTOU race with the + // destructor. The destructor sets _stop, then acquires + // _threads_mutex to snapshot _threads. By checking _stop here + // under the same lock, either: + // (a) we hold the lock first, see _stop==false, detach and + // erase ourselves — the destructor's snapshot won't + // include us, so it won't join a detached thread; or + // (b) the destructor sets _stop first — we see _stop==true + // and bail out, remaining in _threads for the destructor + // to join. if(_stop.load(std::memory_order_acquire)) return false; - mutex_lockguard(_threads_mutex); - if(_threads.size() <= min_count_) return false;