Commit f00b85dbf4 for strongswan.org
commit f00b85dbf45c574271a2cf36f5a863d7b76c1d25
Author: Tobias Brunner <tobias@strongswan.org>
Date: Tue Jul 14 13:12:33 2026 +0200
windows: Fix potential use-after-free when joining a thread
Because the flag was set before running the TLS cleanup, a thread
waiting in `join()` could exit the loop and destroy the thread object
before `docleanup()` is called in `end_thread()`.
This change removes the `terminated` flag and instead properly waits for
the thread to exit in `join()`. By always removing the threads from the
hashtable in `end_thread()`, we also avoid requiring to check any flags
in `cleanup_tls()`, as it now only finds an object for external threads.
We now also make sure to call `docleanup()` before removing the thread
from the hashtable. Otherwise, if a TLS cleanup callback calls
`thread_current[_id]()`, a new thread object would get created that is
never cleaned up.
Fixes: 0fa9c958114f ("windows: Provide a complete native Windows threading backend")
diff --git a/src/libstrongswan/threading/windows/thread.c b/src/libstrongswan/threading/windows/thread.c
index d2a1e83d11..03925c9f41 100644
--- a/src/libstrongswan/threading/windows/thread.c
+++ b/src/libstrongswan/threading/windows/thread.c
@@ -1,4 +1,5 @@
/*
+ * Copyright (C) 2014-2026 Tobias Brunner
* Copyright (C) 2013 Martin Willi
*
* Copyright (C) secunet Security Networks AG
@@ -72,11 +73,6 @@ struct private_thread_t {
*/
hashtable_t *tls;
- /**
- * Thread terminated?
- */
- bool terminated;
-
/**
* Thread detached?
*/
@@ -287,14 +283,12 @@ static void docleanup(private_thread_t *this)
}
/**
- * Clean up and destroy a thread
+ * Destroy a thread
*/
static void destroy(private_thread_t *this)
{
bool old;
- docleanup(this);
-
old = set_leak_detective(FALSE);
array_destroy(this->cleanup);
@@ -309,20 +303,17 @@ static void destroy(private_thread_t *this)
}
/**
- * End a thread, destroy when detached
+ * End and cleanup a thread, destroy when detached
*/
static void end_thread(private_thread_t *this)
{
+ docleanup(this);
+ remove_thread(this);
+
if (this->detached)
{
- remove_thread(this);
destroy(this);
}
- else
- {
- this->terminated = TRUE;
- docleanup(this);
- }
}
/**
@@ -398,15 +389,14 @@ METHOD(thread_t, join, void*,
return NULL;
}
- while (!this->terminated)
+ /* join is a cancellation point, use alertable wait until thread exited */
+ while (WaitForSingleObjectEx(this->handle, INFINITE,
+ TRUE) == WAIT_IO_COMPLETION)
{
- /* join is a cancellation point, use alertable wait */
- WaitForSingleObjectEx(this->handle, INFINITE, TRUE);
}
ret = this->ret;
- remove_thread(this);
destroy(this);
return ret;
@@ -631,7 +621,6 @@ void thread_exit(void *val)
static void cleanup_tls()
{
private_thread_t *this;
- bool old;
/* ignore this if called for the thread that called threads_deinit() */
if (!threads_lock)
@@ -639,23 +628,17 @@ static void cleanup_tls()
return;
}
- old = set_leak_detective(FALSE);
+ /* we target externally spawned threads only here, our threads remove
+ * themselves in end_thread(), which runs before DLL_THREAD_DETACH */
threads_lock->lock(threads_lock);
-
- this = threads->remove(threads, (void*)(uintptr_t)GetCurrentThreadId());
-
+ this = threads->get(threads, (void*)(uintptr_t)GetCurrentThreadId());
threads_lock->unlock(threads_lock);
- set_leak_detective(old);
if (this)
{
- /* If the thread exited, but has not been joined, it is in terminated
- * state. We must not mangle it, as we target externally spawned
- * threads only. */
- if (!this->terminated && !this->detached)
- {
- destroy(this);
- }
+ docleanup(this);
+ remove_thread(this);
+ destroy(this);
}
}
@@ -696,7 +679,9 @@ void threads_deinit()
{
private_thread_t *this;
- this = threads->remove(threads, (void*)(uintptr_t)GetCurrentThreadId());
+ this = get_current_thread();
+ docleanup(this);
+ remove_thread(this);
destroy(this);
threads_lock->destroy(threads_lock);