diff options
author | Rich Felker <dalias@aerifal.cx> | 2022-10-05 11:07:52 -0400 |
---|---|---|
committer | Rich Felker <dalias@aerifal.cx> | 2022-10-19 14:01:32 -0400 |
commit | d64148a8743ad9ed0594091d2ff141b1e9334d4b (patch) | |
tree | 08c424fbd714208272f25a55dc547c0f40c2493a | |
parent | 36b72cd6fdfed2cac6b6ff1ed58a96d8265785cf (diff) | |
download | musl-d64148a8743ad9ed0594091d2ff141b1e9334d4b.tar.gz musl-d64148a8743ad9ed0594091d2ff141b1e9334d4b.tar.xz musl-d64148a8743ad9ed0594091d2ff141b1e9334d4b.zip |
fix potential unsynchronized access to killlock state at thread exit
as reported by Alexey Izbyshev, when the second-to-last thread exits causing a return to single-threaded (no locks needed) state, it creates a situation where the last remaining thread may obtain the killlock that's already held by the exiting thread. this means it may erroneously use the tid of the exiting thread, and may corrupt the lock state due to double-unlock. commit 8d81ba8c0bc6fe31136cb15c9c82ef4c24965040, which (re)introduced the switch back to single-threaded state, documents the intent that the first lock after switching back should provide the necessary synchronization. this is correct, but only works if the switch back is made after there is no further need for synchronization with locks (other than the thread list lock, which can't be bypassed) held by the exiting thread. in order to hit the bug, the remaining thread must first take a different lock, causing it to perform an actual lock one last time, consume the need_locks==-1 state, and transition to need_locks==0. after that, the next attempt to lock the exiting thread's killlock will bypass locking. fix this by reordering the unlocking of killlock at thread exit time, along with changes to the state protected by it, to occur earlier, before the switch to single-threaded state. there are really no constraints on where it's done, except that it occur after there is no longer any possibility of application code executing in the exiting thread, so do it as early as possible.
-rw-r--r-- | src/thread/pthread_create.c | 16 |
1 files changed, 10 insertions, 6 deletions
diff --git a/src/thread/pthread_create.c b/src/thread/pthread_create.c index 6f187ee8..087f6206 100644 --- a/src/thread/pthread_create.c +++ b/src/thread/pthread_create.c @@ -107,6 +107,16 @@ _Noreturn void __pthread_exit(void *result) /* At this point we are committed to thread termination. */ + /* After the kernel thread exits, its tid may be reused. Clear it + * to prevent inadvertent use and inform functions that would use + * it that it's no longer available. At this point the killlock + * may be released, since functions that use it will consistently + * see the thread as having exited. Release it now so that no + * remaining locks (except thread list) are held if we end up + * resetting need_locks below. */ + self->tid = 0; + UNLOCK(self->killlock); + /* Process robust list in userspace to handle non-pshared mutexes * and the detached thread case where the robust list head will * be invalid when the kernel would process it. */ @@ -159,12 +169,6 @@ _Noreturn void __pthread_exit(void *result) a_store(&self->detach_state, DT_EXITED); __wake(&self->detach_state, 1, 1); - /* After the kernel thread exits, its tid may be reused. Clear it - * to prevent inadvertent use and inform functions that would use - * it that it's no longer available. */ - self->tid = 0; - UNLOCK(self->killlock); - for (;;) __syscall(SYS_exit, 0); } |