e5e4d7cc05
POSIX requires that dlclose() and exit() be thread safe, therefore you can have one thread in the middle of dlclose() and another thread executing exit() without causing any undefined behaviour on the part of the implementation. The existing implementation had a flaw that exit() exit handler processing did not consider a concurrent dlclose() and would not mark already run exit handlers using the ef_free flavour. The consequence of this is that a concurrent exit() with dlclose() will run all the exit handlers that dlclose() had not yet run, but then will block on the loader lock. The concurrent dlclose() will continue to run all the exit handlers again (twice) in violation of the Itanium C++ ABI requirements for __cxa_atexit(). This commit fixes this by having exit() mark all handlers with ef_free to ensure that concurrent dlclose() won't re-run registered exit handlers that have already run.
142 lines
3.9 KiB
C
142 lines
3.9 KiB
C
/* Copyright (C) 1991-2017 Free Software Foundation, Inc.
|
|
This file is part of the GNU C Library.
|
|
|
|
The GNU C Library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
The GNU C Library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with the GNU C Library; if not, see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sysdep.h>
|
|
#include <libc-lock.h>
|
|
#include "exit.h"
|
|
|
|
#include "set-hooks.h"
|
|
DEFINE_HOOK (__libc_atexit, (void))
|
|
|
|
/* Initialize the flag that indicates exit function processing
|
|
is complete. See concurrency notes in stdlib/exit.h where
|
|
__exit_funcs_lock is declared. */
|
|
bool __exit_funcs_done = false;
|
|
|
|
/* Call all functions registered with `atexit' and `on_exit',
|
|
in the reverse of the order in which they were registered
|
|
perform stdio cleanup, and terminate program execution with STATUS. */
|
|
void
|
|
attribute_hidden
|
|
__run_exit_handlers (int status, struct exit_function_list **listp,
|
|
bool run_list_atexit, bool run_dtors)
|
|
{
|
|
/* First, call the TLS destructors. */
|
|
#ifndef SHARED
|
|
if (&__call_tls_dtors != NULL)
|
|
#endif
|
|
if (run_dtors)
|
|
__call_tls_dtors ();
|
|
|
|
/* We do it this way to handle recursive calls to exit () made by
|
|
the functions registered with `atexit' and `on_exit'. We call
|
|
everyone on the list and use the status value in the last
|
|
exit (). */
|
|
while (true)
|
|
{
|
|
struct exit_function_list *cur;
|
|
|
|
__libc_lock_lock (__exit_funcs_lock);
|
|
|
|
restart:
|
|
cur = *listp;
|
|
|
|
if (cur == NULL)
|
|
{
|
|
/* Exit processing complete. We will not allow any more
|
|
atexit/on_exit registrations. */
|
|
__exit_funcs_done = true;
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
break;
|
|
}
|
|
|
|
while (cur->idx > 0)
|
|
{
|
|
struct exit_function *const f = &cur->fns[--cur->idx];
|
|
const uint64_t new_exitfn_called = __new_exitfn_called;
|
|
|
|
/* Unlock the list while we call a foreign function. */
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
switch (f->flavor)
|
|
{
|
|
void (*atfct) (void);
|
|
void (*onfct) (int status, void *arg);
|
|
void (*cxafct) (void *arg, int status);
|
|
|
|
case ef_free:
|
|
case ef_us:
|
|
break;
|
|
case ef_on:
|
|
onfct = f->func.on.fn;
|
|
#ifdef PTR_DEMANGLE
|
|
PTR_DEMANGLE (onfct);
|
|
#endif
|
|
onfct (status, f->func.on.arg);
|
|
break;
|
|
case ef_at:
|
|
atfct = f->func.at;
|
|
#ifdef PTR_DEMANGLE
|
|
PTR_DEMANGLE (atfct);
|
|
#endif
|
|
atfct ();
|
|
break;
|
|
case ef_cxa:
|
|
/* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
|
|
we must mark this function as ef_free. */
|
|
f->flavor = ef_free;
|
|
cxafct = f->func.cxa.fn;
|
|
#ifdef PTR_DEMANGLE
|
|
PTR_DEMANGLE (cxafct);
|
|
#endif
|
|
cxafct (f->func.cxa.arg, status);
|
|
break;
|
|
}
|
|
/* Re-lock again before looking at global state. */
|
|
__libc_lock_lock (__exit_funcs_lock);
|
|
|
|
if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
|
|
/* The last exit function, or another thread, has registered
|
|
more exit functions. Start the loop over. */
|
|
goto restart;
|
|
}
|
|
|
|
*listp = cur->next;
|
|
if (*listp != NULL)
|
|
/* Don't free the last element in the chain, this is the statically
|
|
allocate element. */
|
|
free (cur);
|
|
|
|
__libc_lock_unlock (__exit_funcs_lock);
|
|
}
|
|
|
|
if (run_list_atexit)
|
|
RUN_HOOK (__libc_atexit, ());
|
|
|
|
_exit (status);
|
|
}
|
|
|
|
|
|
void
|
|
exit (int status)
|
|
{
|
|
__run_exit_handlers (status, &__exit_funcs, true, true);
|
|
}
|
|
libc_hidden_def (exit)
|