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: 0fa9c95811 ("windows: Provide a complete native Windows threading backend")
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2014-2026 Tobias Brunner
|
||||||
* Copyright (C) 2013 Martin Willi
|
* Copyright (C) 2013 Martin Willi
|
||||||
*
|
*
|
||||||
* Copyright (C) secunet Security Networks AG
|
* Copyright (C) secunet Security Networks AG
|
||||||
@@ -72,11 +73,6 @@ struct private_thread_t {
|
|||||||
*/
|
*/
|
||||||
hashtable_t *tls;
|
hashtable_t *tls;
|
||||||
|
|
||||||
/**
|
|
||||||
* Thread terminated?
|
|
||||||
*/
|
|
||||||
bool terminated;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thread detached?
|
* 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)
|
static void destroy(private_thread_t *this)
|
||||||
{
|
{
|
||||||
bool old;
|
bool old;
|
||||||
|
|
||||||
docleanup(this);
|
|
||||||
|
|
||||||
old = set_leak_detective(FALSE);
|
old = set_leak_detective(FALSE);
|
||||||
|
|
||||||
array_destroy(this->cleanup);
|
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)
|
static void end_thread(private_thread_t *this)
|
||||||
{
|
{
|
||||||
|
docleanup(this);
|
||||||
|
remove_thread(this);
|
||||||
|
|
||||||
if (this->detached)
|
if (this->detached)
|
||||||
{
|
{
|
||||||
remove_thread(this);
|
|
||||||
destroy(this);
|
destroy(this);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
this->terminated = TRUE;
|
|
||||||
docleanup(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -398,15 +389,14 @@ METHOD(thread_t, join, void*,
|
|||||||
return NULL;
|
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;
|
ret = this->ret;
|
||||||
|
|
||||||
remove_thread(this);
|
|
||||||
destroy(this);
|
destroy(this);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -631,7 +621,6 @@ void thread_exit(void *val)
|
|||||||
static void cleanup_tls()
|
static void cleanup_tls()
|
||||||
{
|
{
|
||||||
private_thread_t *this;
|
private_thread_t *this;
|
||||||
bool old;
|
|
||||||
|
|
||||||
/* ignore this if called for the thread that called threads_deinit() */
|
/* ignore this if called for the thread that called threads_deinit() */
|
||||||
if (!threads_lock)
|
if (!threads_lock)
|
||||||
@@ -639,23 +628,17 @@ static void cleanup_tls()
|
|||||||
return;
|
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);
|
threads_lock->lock(threads_lock);
|
||||||
|
this = threads->get(threads, (void*)(uintptr_t)GetCurrentThreadId());
|
||||||
this = threads->remove(threads, (void*)(uintptr_t)GetCurrentThreadId());
|
|
||||||
|
|
||||||
threads_lock->unlock(threads_lock);
|
threads_lock->unlock(threads_lock);
|
||||||
set_leak_detective(old);
|
|
||||||
|
|
||||||
if (this)
|
if (this)
|
||||||
{
|
{
|
||||||
/* If the thread exited, but has not been joined, it is in terminated
|
docleanup(this);
|
||||||
* state. We must not mangle it, as we target externally spawned
|
remove_thread(this);
|
||||||
* threads only. */
|
destroy(this);
|
||||||
if (!this->terminated && !this->detached)
|
|
||||||
{
|
|
||||||
destroy(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -696,7 +679,9 @@ void threads_deinit()
|
|||||||
{
|
{
|
||||||
private_thread_t *this;
|
private_thread_t *this;
|
||||||
|
|
||||||
this = threads->remove(threads, (void*)(uintptr_t)GetCurrentThreadId());
|
this = get_current_thread();
|
||||||
|
docleanup(this);
|
||||||
|
remove_thread(this);
|
||||||
destroy(this);
|
destroy(this);
|
||||||
|
|
||||||
threads_lock->destroy(threads_lock);
|
threads_lock->destroy(threads_lock);
|
||||||
|
|||||||
Reference in New Issue
Block a user