always block signals in pthread_exit before decrementing thread count
authorRich Felker <dalias@aerifal.cx>
Fri, 26 Apr 2013 19:47:44 +0000 (15:47 -0400)
committerRich Felker <dalias@aerifal.cx>
Fri, 26 Apr 2013 19:47:44 +0000 (15:47 -0400)
the thread count (1+libc.threads_minus_1) must always be greater than
or equal to the number of threads which could have application code
running, even in an async-signal-safe sense. there is at least one
dangerous race condition if this invariant fails to hold: dlopen could
allocate too little TLS for existing threads, and a signal handler
running in the exiting thread could claim the allocated TLS for itself
(via __tls_get_addr), leaving too little for the other threads it was
allocated for and thereby causing out-of-bounds access.

there may be other situations where it's dangerous for the thread
count to be too low, particularly in the case where only one thread
should be left, in which case locking may be omitted. however, all
such code paths seem to arise from undefined behavior, since
async-signal-unsafe functions are not permitted to be called from a
signal handler that interrupts pthread_exit (which is itself
async-signal-unsafe).

this change may also simplify logic in __synccall and improve the
chances of making __synccall async-signal-safe.

src/thread/pthread_create.c

index e6760ab..0cd2d6c 100644 (file)
@@ -32,6 +32,8 @@ _Noreturn void pthread_exit(void *result)
        self->dead = 1;
        __unlock(self->killlock);
 
        self->dead = 1;
        __unlock(self->killlock);
 
+       __syscall(SYS_rt_sigprocmask, SIG_BLOCK, SIGALL_SET, 0, _NSIG/8);
+
        do n = libc.threads_minus_1;
        while (n && a_cas(&libc.threads_minus_1, n, n-1)!=n);
        if (!n) exit(0);
        do n = libc.threads_minus_1;
        while (n && a_cas(&libc.threads_minus_1, n, n-1)!=n);
        if (!n) exit(0);
@@ -39,8 +41,6 @@ _Noreturn void pthread_exit(void *result)
        if (self->detached && self->map_base) {
                if (self->detached == 2)
                        __syscall(SYS_set_tid_address, 0);
        if (self->detached && self->map_base) {
                if (self->detached == 2)
                        __syscall(SYS_set_tid_address, 0);
-               __syscall(SYS_rt_sigprocmask, SIG_BLOCK,
-                       SIGALL_SET, 0, _NSIG/8);
                __unmapself(self->map_base, self->map_size);
        }
 
                __unmapself(self->map_base, self->map_size);
        }