]> git.feebdaed.xyz Git - 0xmirror/glibc.git/commit
nptl: Do not use pthread set_tid_address as state synchronization (BZ #19951)
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Thu, 11 Dec 2025 20:47:19 +0000 (17:47 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Fri, 19 Dec 2025 16:23:05 +0000 (13:23 -0300)
commit5da15b15adab661c80e373b6af89be0b5fa5b3ad
tree42119de7de11ba85a83510361879ebf8df7d1418
parent2d865eaa12def42a713b279dba992536ee372ca8
nptl: Do not use pthread set_tid_address as state synchronization (BZ #19951)

The use-after-free described in BZ#19951 is due to the use of two
different PD fields, 'joinid' and 'cancelhandling', to describe the
thread state and to synchronise the calls of pthread_join,
pthread_detach, pthread_exit, and normal thread exit.

Any state change may require checking both fields atomically to handle
partial state (e.g., pthread_join() with a cancellation handler to
issue a 'joinstate' field rollback).

This patch uses a different PD member with 4 possible states (JOINABLE,
DETACHED, EXITING, and EXITED) instead of the pthread 'tid' field, with
the following logic:

 1. On pthread_create, the initial state is set either to JOINABLE or
    DETACHED depending on the pthread attribute used.

 2. On pthread_detach, a CAS is issued on the state.  If the CAS fails,
    the thread is already detached (DETACHED) or being terminated (EXITING).
    For the former, an EINVAL is returned; for the latter, pthread_detach
    should be responsible for joining the thread (and for deallocating any
    internal resources).

 3. In the exit phase of the wrapper function for the thread start routine
    (reached either if the thread function has returned, pthread_exit has
    been called, or cancellation handled has been acted upon), we issue a
    CAS on state to set it to the EXITING mode.

    If the thread is previously in DETACHED mode, the thread is responsible
    for deallocating any resources; otherwise, the thread must be joined
    (detached threads cannot deallocate themselves immediately).

 4. The clear_tid_field on 'clone' call is changed to set the new 'state'
    field on thread exit (EXITED).  This state is only reached at thread
    termination.

 5. The pthread_join implementation is now simpler: the futex wait is done
    directly on thread state, and there is no need to reset it in case of
    timeout since the state is now set either by pthread_detach() or by the
    kernel on process termination.

The race condition on pthread_detach is avoided with a single atomic
operation on the PD state: once the mode is set to THREAD_STATE_DETACHED, it
is up to the thread itself to deallocate its memory (done during the exit
phase at pthread_create()).

Also, the INVALID_NOT_TERMINATED_TD_P is removed since a negative yid is
not possible, and the macro is not used anywhere.

This change triggers an invalid C11 thread test: it creates a thread that
detaches, and after a timeout, the creating thread checks whether the join
fails.  The issue is that once thrd_join() is called, the thread's lifetime
is not defined.

Checked on x86_64-linux-gnu, i686-linux-gnu, aarch64-linux-gnu,
arm-linux-gnueabihf, and powerpc64-linux-gnu.

Reviewed-by: Florian Weimer <fweimer@redhat.com>
12 files changed:
nptl/descr.h
nptl/nptl-stack.h
nptl/pthread_cancel.c
nptl/pthread_create.c
nptl/pthread_detach.c
nptl/pthread_getattr_np.c
nptl/pthread_join_common.c
nptl/pthread_tryjoin.c
sysdeps/nptl/dl-tls_init_tp.c
sysdeps/nptl/libc_start_call_main.h
sysdeps/nptl/pthreadP.h
sysdeps/pthread/tst-thrd-detach.c