From ea295f68f1fae7c701baaa717f67296659d567ac Mon Sep 17 00:00:00 2001 From: Dmitriy Ivanov Date: Thu, 20 Nov 2014 20:47:02 -0800 Subject: Unregister pthread_atfork handlers on dlclose() Bug: http://b/20339788 Change-Id: I874c87faa377645fa9e0752f4fc166d81fd9ef7e --- libc/Android.mk | 17 +- libc/arch-arm64/bionic/crtbegin.c | 1 + libc/arch-common/bionic/crtbegin.c | 1 + libc/arch-common/bionic/crtbegin_so.c | 1 + libc/arch-common/bionic/pthread_atfork.h | 29 ++++ libc/arch-mips/bionic/crtbegin.c | 1 + libc/bionic/pthread_atfork.cpp | 122 ++++++++++---- libc/stdlib/atexit.c | 212 +++++++++++++++++++++++++ libc/stdlib/atexit.h | 47 ++++++ libc/upstream-openbsd/lib/libc/stdlib/atexit.c | 202 ----------------------- libc/upstream-openbsd/lib/libc/stdlib/atexit.h | 47 ------ tests/libs/Android.build.pthread_atfork.mk | 25 +++ tests/libs/Android.mk | 6 + tests/libs/pthread_atfork.cpp | 21 +++ tests/pthread_test.cpp | 77 +++++++-- 15 files changed, 516 insertions(+), 293 deletions(-) create mode 100644 libc/arch-common/bionic/pthread_atfork.h create mode 100644 libc/stdlib/atexit.c create mode 100644 libc/stdlib/atexit.h delete mode 100644 libc/upstream-openbsd/lib/libc/stdlib/atexit.c delete mode 100644 libc/upstream-openbsd/lib/libc/stdlib/atexit.h create mode 100644 tests/libs/Android.build.pthread_atfork.mk create mode 100644 tests/libs/pthread_atfork.cpp diff --git a/libc/Android.mk b/libc/Android.mk index 2175dc4..a9fed2c 100644 --- a/libc/Android.mk +++ b/libc/Android.mk @@ -63,6 +63,7 @@ libc_common_src_files := \ stdio/sprintf.c \ stdio/stdio.c \ stdio/stdio_ext.cpp \ + stdlib/atexit.c \ stdlib/exit.c \ # Fortify implementations of libc functions. @@ -482,7 +483,6 @@ libc_upstream_openbsd_ndk_src_files := \ upstream-openbsd/lib/libc/stdio/wprintf.c \ upstream-openbsd/lib/libc/stdio/wscanf.c \ upstream-openbsd/lib/libc/stdio/wsetup.c \ - upstream-openbsd/lib/libc/stdlib/atexit.c \ upstream-openbsd/lib/libc/stdlib/atoi.c \ upstream-openbsd/lib/libc/stdlib/atol.c \ upstream-openbsd/lib/libc/stdlib/atoll.c \ @@ -1340,10 +1340,13 @@ LOCAL_CPPFLAGS := $(libc_common_cppflags) LOCAL_C_INCLUDES := $(libc_common_c_includes) LOCAL_SRC_FILES := \ + arch-common/bionic/crtbegin_so.c \ + arch-common/bionic/crtbrand.S \ $(libc_arch_dynamic_src_files) \ bionic/malloc_debug_common.cpp \ bionic/libc_init_dynamic.cpp \ bionic/NetdClient.cpp \ + arch-common/bionic/crtend_so.S \ LOCAL_MODULE := libc LOCAL_CLANG := $(use_clang) @@ -1388,15 +1391,15 @@ LOCAL_LDFLAGS_arm := -Wl,--hash-style=sysv $(eval $(call patch-up-arch-specific-flags,LOCAL_CFLAGS,libc_common_cflags)) $(eval $(call patch-up-arch-specific-flags,LOCAL_SRC_FILES,libc_arch_dynamic_src_files)) + +LOCAL_NO_CRT := true +LOCAL_ASFLAGS += $(libc_crt_target_cflags) + # special for arm -LOCAL_NO_CRT_arm := true LOCAL_CFLAGS_arm += -DCRT_LEGACY_WORKAROUND -LOCAL_ASFLAGS_arm += $(libc_crt_target_cflags) LOCAL_SRC_FILES_arm += \ - arch-common/bionic/crtbegin_so.c \ - arch-common/bionic/crtbrand.S \ - arch-arm/bionic/atexit_legacy.c \ - arch-common/bionic/crtend_so.S + arch-arm/bionic/atexit_legacy.c + LOCAL_ADDRESS_SANITIZER := false LOCAL_NATIVE_COVERAGE := $(bionic_coverage) diff --git a/libc/arch-arm64/bionic/crtbegin.c b/libc/arch-arm64/bionic/crtbegin.c index fec0b11..7e2c5d7 100644 --- a/libc/arch-arm64/bionic/crtbegin.c +++ b/libc/arch-arm64/bionic/crtbegin.c @@ -67,3 +67,4 @@ __asm__ ( #include "../../arch-common/bionic/__dso_handle.h" #include "../../arch-common/bionic/atexit.h" +#include "../../arch-common/bionic/pthread_atfork.h" diff --git a/libc/arch-common/bionic/crtbegin.c b/libc/arch-common/bionic/crtbegin.c index fa9f3f3..c46405c 100644 --- a/libc/arch-common/bionic/crtbegin.c +++ b/libc/arch-common/bionic/crtbegin.c @@ -59,6 +59,7 @@ void _start() { #include "__dso_handle.h" #include "atexit.h" +#include "pthread_atfork.h" #ifdef __i386__ # include "../../arch-x86/bionic/__stack_chk_fail_local.h" #endif diff --git a/libc/arch-common/bionic/crtbegin_so.c b/libc/arch-common/bionic/crtbegin_so.c index 641e45a..3754363 100644 --- a/libc/arch-common/bionic/crtbegin_so.c +++ b/libc/arch-common/bionic/crtbegin_so.c @@ -56,6 +56,7 @@ void __on_dlclose() { # include "__dso_handle_so.h" # include "atexit.h" #endif +#include "pthread_atfork.h" #ifdef __i386__ # include "../../arch-x86/bionic/__stack_chk_fail_local.h" #endif diff --git a/libc/arch-common/bionic/pthread_atfork.h b/libc/arch-common/bionic/pthread_atfork.h new file mode 100644 index 0000000..0c48a12 --- /dev/null +++ b/libc/arch-common/bionic/pthread_atfork.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +extern void* __dso_handle; + +extern int __register_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void), void* dso); + +#ifndef _LIBC +// Libc used to export this in previous versions, therefore it needs +// to remain global for binary compatibility. +__attribute__ ((visibility ("hidden"))) +#endif +int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)) { + return __register_atfork(prepare, parent, child, &__dso_handle); +} + diff --git a/libc/arch-mips/bionic/crtbegin.c b/libc/arch-mips/bionic/crtbegin.c index 50e9eeb..d72ec7b 100644 --- a/libc/arch-mips/bionic/crtbegin.c +++ b/libc/arch-mips/bionic/crtbegin.c @@ -92,3 +92,4 @@ __asm__ ( #include "../../arch-common/bionic/__dso_handle.h" #include "../../arch-common/bionic/atexit.h" +#include "../../arch-common/bionic/pthread_atfork.h" diff --git a/libc/bionic/pthread_atfork.cpp b/libc/bionic/pthread_atfork.cpp index d1c4ad0..093ffd2 100644 --- a/libc/bionic/pthread_atfork.cpp +++ b/libc/bionic/pthread_atfork.cpp @@ -30,6 +30,8 @@ #include #include +#include "private/bionic_macros.h" + struct atfork_t { atfork_t* next; atfork_t* prev; @@ -37,79 +39,143 @@ struct atfork_t { void (*prepare)(void); void (*child)(void); void (*parent)(void); + + void* dso_handle; }; -struct atfork_list_t { - atfork_t* first; - atfork_t* last; +class atfork_list_t { + public: + atfork_list_t() : first_(nullptr), last_(nullptr) {} + + template + void walk_forward(F f) { + for (atfork_t* it = first_; it != nullptr; it = it->next) { + f(it); + } + } + + template + void walk_backwards(F f) { + for (atfork_t* it = last_; it != nullptr; it = it->prev) { + f(it); + } + } + + void push_back(atfork_t* entry) { + entry->next = nullptr; + entry->prev = last_; + if (entry->prev != nullptr) { + entry->prev->next = entry; + } + if (first_ == nullptr) { + first_ = entry; + } + last_ = entry; + } + + template + void remove_if(F predicate) { + atfork_t* it = first_; + while (it != nullptr) { + if (predicate(it)) { + atfork_t* entry = it; + it = it->next; + remove(entry); + } else { + it = it->next; + } + } + } + + private: + void remove(atfork_t* entry) { + if (entry->prev != nullptr) { + entry->prev->next = entry->next; + } else { + first_ = entry->next; + } + + if (entry->next != nullptr) { + entry->next->prev = entry->prev; + } else { + last_ = entry->prev; + } + + free(entry); + } + + atfork_t* first_; + atfork_t* last_; + + DISALLOW_COPY_AND_ASSIGN(atfork_list_t); }; static pthread_mutex_t g_atfork_list_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; -static atfork_list_t g_atfork_list = { NULL, NULL }; +static atfork_list_t g_atfork_list; void __bionic_atfork_run_prepare() { // We lock the atfork list here, unlock it in the parent, and reset it in the child. // This ensures that nobody can modify the handler array between the calls // to the prepare and parent/child handlers. - // - // TODO: If a handler tries to mutate the list, they'll block. We should probably copy - // the list before forking, and have prepare, parent, and child all work on the consistent copy. pthread_mutex_lock(&g_atfork_list_mutex); // Call pthread_atfork() prepare handlers. POSIX states that the prepare // handlers should be called in the reverse order of the parent/child // handlers, so we iterate backwards. - for (atfork_t* it = g_atfork_list.last; it != NULL; it = it->prev) { - if (it->prepare != NULL) { + g_atfork_list.walk_backwards([](atfork_t* it) { + if (it->prepare != nullptr) { it->prepare(); } - } + }); } void __bionic_atfork_run_child() { - for (atfork_t* it = g_atfork_list.first; it != NULL; it = it->next) { - if (it->child != NULL) { + g_atfork_list.walk_forward([](atfork_t* it) { + if (it->child != nullptr) { it->child(); } - } + }); g_atfork_list_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; } void __bionic_atfork_run_parent() { - for (atfork_t* it = g_atfork_list.first; it != NULL; it = it->next) { - if (it->parent != NULL) { + g_atfork_list.walk_forward([](atfork_t* it) { + if (it->parent != nullptr) { it->parent(); } - } + }); pthread_mutex_unlock(&g_atfork_list_mutex); } -int pthread_atfork(void (*prepare)(void), void (*parent)(void), void(*child)(void)) { +// __register_atfork is the name used by glibc +extern "C" int __register_atfork(void (*prepare)(void), void (*parent)(void), + void(*child)(void), void* dso) { atfork_t* entry = reinterpret_cast(malloc(sizeof(atfork_t))); - if (entry == NULL) { + if (entry == nullptr) { return ENOMEM; } entry->prepare = prepare; entry->parent = parent; entry->child = child; + entry->dso_handle = dso; pthread_mutex_lock(&g_atfork_list_mutex); - // Append 'entry' to the list. - entry->next = NULL; - entry->prev = g_atfork_list.last; - if (entry->prev != NULL) { - entry->prev->next = entry; - } - if (g_atfork_list.first == NULL) { - g_atfork_list.first = entry; - } - g_atfork_list.last = entry; + g_atfork_list.push_back(entry); pthread_mutex_unlock(&g_atfork_list_mutex); return 0; } + +extern "C" __LIBC_HIDDEN__ void __unregister_atfork(void* dso) { + pthread_mutex_lock(&g_atfork_list_mutex); + g_atfork_list.remove_if([&](const atfork_t* entry) { + return entry->dso_handle == dso; + }); + pthread_mutex_unlock(&g_atfork_list_mutex); +} + diff --git a/libc/stdlib/atexit.c b/libc/stdlib/atexit.c new file mode 100644 index 0000000..df2b1b5 --- /dev/null +++ b/libc/stdlib/atexit.c @@ -0,0 +1,212 @@ +/* $OpenBSD: atexit.c,v 1.20 2014/07/11 09:51:37 kettenis Exp $ */ +/* + * Copyright (c) 2002 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include "atexit.h" +#include "private/thread_private.h" + +struct atexit *__atexit; +static int restartloop; + +/* BEGIN android-changed: __unregister_atfork is used by __cxa_finalize */ +extern void __unregister_atfork(void* dso); +/* END android-changed */ + +/* + * Function pointers are stored in a linked list of pages. The list + * is initially empty, and pages are allocated on demand. The first + * function pointer in the first allocated page (the last one in + * the linked list) is reserved for the cleanup function. + * + * Outside the following functions, all pages are mprotect()'ed + * to prevent unintentional/malicious corruption. + */ + +/* + * Register a function to be performed at exit or when a shared object + * with the given dso handle is unloaded dynamically. Also used as + * the backend for atexit(). For more info on this API, see: + * + * http://www.codesourcery.com/cxx-abi/abi.html#dso-dtor + */ +int +__cxa_atexit(void (*func)(void *), void *arg, void *dso) +{ + struct atexit *p = __atexit; + struct atexit_fn *fnp; + size_t pgsize = getpagesize(); + int ret = -1; + + if (pgsize < sizeof(*p)) + return (-1); + _ATEXIT_LOCK(); + p = __atexit; + if (p != NULL) { + if (p->ind + 1 >= p->max) + p = NULL; + else if (mprotect(p, pgsize, PROT_READ | PROT_WRITE)) + goto unlock; + } + if (p == NULL) { + p = mmap(NULL, pgsize, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + if (p == MAP_FAILED) + goto unlock; + if (__atexit == NULL) { + memset(&p->fns[0], 0, sizeof(p->fns[0])); + p->ind = 1; + } else + p->ind = 0; + p->max = (pgsize - ((char *)&p->fns[0] - (char *)p)) / + sizeof(p->fns[0]); + p->next = __atexit; + __atexit = p; + } + fnp = &p->fns[p->ind++]; + fnp->fn_ptr = func; + fnp->fn_arg = arg; + fnp->fn_dso = dso; + if (mprotect(p, pgsize, PROT_READ)) + goto unlock; + restartloop = 1; + ret = 0; +unlock: + _ATEXIT_UNLOCK(); + return (ret); +} + +/* + * Call all handlers registered with __cxa_atexit() for the shared + * object owning 'dso'. + * Note: if 'dso' is NULL, then all remaining handlers are called. + */ +void +__cxa_finalize(void *dso) +{ + struct atexit *p, *q; + struct atexit_fn fn; + int n, pgsize = getpagesize(); + static int call_depth; + + _ATEXIT_LOCK(); + call_depth++; + +restart: + restartloop = 0; + for (p = __atexit; p != NULL; p = p->next) { + for (n = p->ind; --n >= 0;) { + if (p->fns[n].fn_ptr == NULL) + continue; /* already called */ + if (dso != NULL && dso != p->fns[n].fn_dso) + continue; /* wrong DSO */ + + /* + * Mark handler as having been already called to avoid + * dupes and loops, then call the appropriate function. + */ + fn = p->fns[n]; + if (mprotect(p, pgsize, PROT_READ | PROT_WRITE) == 0) { + p->fns[n].fn_ptr = NULL; + mprotect(p, pgsize, PROT_READ); + } + _ATEXIT_UNLOCK(); + (*fn.fn_ptr)(fn.fn_arg); + _ATEXIT_LOCK(); + if (restartloop) + goto restart; + } + } + + call_depth--; + + /* + * If called via exit(), unmap the pages since we have now run + * all the handlers. We defer this until calldepth == 0 so that + * we don't unmap things prematurely if called recursively. + */ + if (dso == NULL && call_depth == 0) { + for (p = __atexit; p != NULL; ) { + q = p; + p = p->next; + munmap(q, pgsize); + } + __atexit = NULL; + } + _ATEXIT_UNLOCK(); + + /* BEGIN android-changed: call __unregister_atfork if dso is not null */ + if (dso != NULL) { + __unregister_atfork(dso); + } + /* END android-changed */ +} + +/* + * Register the cleanup function + */ +void +__atexit_register_cleanup(void (*func)(void)) +{ + struct atexit *p; + size_t pgsize = getpagesize(); + + if (pgsize < sizeof(*p)) + return; + _ATEXIT_LOCK(); + p = __atexit; + while (p != NULL && p->next != NULL) + p = p->next; + if (p == NULL) { + p = mmap(NULL, pgsize, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + if (p == MAP_FAILED) + goto unlock; + p->ind = 1; + p->max = (pgsize - ((char *)&p->fns[0] - (char *)p)) / + sizeof(p->fns[0]); + p->next = NULL; + __atexit = p; + } else { + if (mprotect(p, pgsize, PROT_READ | PROT_WRITE)) + goto unlock; + } + p->fns[0].fn_ptr = (void (*)(void *))func; + p->fns[0].fn_arg = NULL; + p->fns[0].fn_dso = NULL; + mprotect(p, pgsize, PROT_READ); + restartloop = 1; +unlock: + _ATEXIT_UNLOCK(); +} diff --git a/libc/stdlib/atexit.h b/libc/stdlib/atexit.h new file mode 100644 index 0000000..3de2aa3 --- /dev/null +++ b/libc/stdlib/atexit.h @@ -0,0 +1,47 @@ +/* $OpenBSD: atexit.h,v 1.9 2014/06/18 19:01:10 kettenis Exp $ */ + +/* + * Copyright (c) 2002 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +struct atexit { + struct atexit *next; /* next in list */ + int ind; /* next index in this table */ + int max; /* max entries >= ATEXIT_SIZE */ + struct atexit_fn { + void (*fn_ptr)(void *); + void *fn_arg; /* argument for CXA callback */ + void *fn_dso; /* shared module handle */ + } fns[1]; /* the table itself */ +}; + +extern struct atexit *__atexit; /* points to head of LIFO stack */ + +int __cxa_atexit(void (*)(void *), void *, void *); +void __cxa_finalize(void *); diff --git a/libc/upstream-openbsd/lib/libc/stdlib/atexit.c b/libc/upstream-openbsd/lib/libc/stdlib/atexit.c deleted file mode 100644 index 6532b38..0000000 --- a/libc/upstream-openbsd/lib/libc/stdlib/atexit.c +++ /dev/null @@ -1,202 +0,0 @@ -/* $OpenBSD: atexit.c,v 1.20 2014/07/11 09:51:37 kettenis Exp $ */ -/* - * Copyright (c) 2002 Daniel Hartmeier - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include -#include -#include -#include -#include -#include "atexit.h" -#include "thread_private.h" - -struct atexit *__atexit; -static int restartloop; - -/* - * Function pointers are stored in a linked list of pages. The list - * is initially empty, and pages are allocated on demand. The first - * function pointer in the first allocated page (the last one in - * the linked list) is reserved for the cleanup function. - * - * Outside the following functions, all pages are mprotect()'ed - * to prevent unintentional/malicious corruption. - */ - -/* - * Register a function to be performed at exit or when a shared object - * with the given dso handle is unloaded dynamically. Also used as - * the backend for atexit(). For more info on this API, see: - * - * http://www.codesourcery.com/cxx-abi/abi.html#dso-dtor - */ -int -__cxa_atexit(void (*func)(void *), void *arg, void *dso) -{ - struct atexit *p = __atexit; - struct atexit_fn *fnp; - int pgsize = getpagesize(); - int ret = -1; - - if (pgsize < sizeof(*p)) - return (-1); - _ATEXIT_LOCK(); - p = __atexit; - if (p != NULL) { - if (p->ind + 1 >= p->max) - p = NULL; - else if (mprotect(p, pgsize, PROT_READ | PROT_WRITE)) - goto unlock; - } - if (p == NULL) { - p = mmap(NULL, pgsize, PROT_READ | PROT_WRITE, - MAP_ANON | MAP_PRIVATE, -1, 0); - if (p == MAP_FAILED) - goto unlock; - if (__atexit == NULL) { - memset(&p->fns[0], 0, sizeof(p->fns[0])); - p->ind = 1; - } else - p->ind = 0; - p->max = (pgsize - ((char *)&p->fns[0] - (char *)p)) / - sizeof(p->fns[0]); - p->next = __atexit; - __atexit = p; - } - fnp = &p->fns[p->ind++]; - fnp->fn_ptr = func; - fnp->fn_arg = arg; - fnp->fn_dso = dso; - if (mprotect(p, pgsize, PROT_READ)) - goto unlock; - restartloop = 1; - ret = 0; -unlock: - _ATEXIT_UNLOCK(); - return (ret); -} - -/* - * Call all handlers registered with __cxa_atexit() for the shared - * object owning 'dso'. - * Note: if 'dso' is NULL, then all remaining handlers are called. - */ -void -__cxa_finalize(void *dso) -{ - struct atexit *p, *q; - struct atexit_fn fn; - int n, pgsize = getpagesize(); - static int call_depth; - - _ATEXIT_LOCK(); - call_depth++; - -restart: - restartloop = 0; - for (p = __atexit; p != NULL; p = p->next) { - for (n = p->ind; --n >= 0;) { - if (p->fns[n].fn_ptr == NULL) - continue; /* already called */ - if (dso != NULL && dso != p->fns[n].fn_dso) - continue; /* wrong DSO */ - - /* - * Mark handler as having been already called to avoid - * dupes and loops, then call the appropriate function. - */ - fn = p->fns[n]; - if (mprotect(p, pgsize, PROT_READ | PROT_WRITE) == 0) { - p->fns[n].fn_ptr = NULL; - mprotect(p, pgsize, PROT_READ); - } - _ATEXIT_UNLOCK(); - (*fn.fn_ptr)(fn.fn_arg); - _ATEXIT_LOCK(); - if (restartloop) - goto restart; - } - } - - call_depth--; - - /* - * If called via exit(), unmap the pages since we have now run - * all the handlers. We defer this until calldepth == 0 so that - * we don't unmap things prematurely if called recursively. - */ - if (dso == NULL && call_depth == 0) { - for (p = __atexit; p != NULL; ) { - q = p; - p = p->next; - munmap(q, pgsize); - } - __atexit = NULL; - } - _ATEXIT_UNLOCK(); -} - -/* - * Register the cleanup function - */ -void -__atexit_register_cleanup(void (*func)(void)) -{ - struct atexit *p; - int pgsize = getpagesize(); - - if (pgsize < sizeof(*p)) - return; - _ATEXIT_LOCK(); - p = __atexit; - while (p != NULL && p->next != NULL) - p = p->next; - if (p == NULL) { - p = mmap(NULL, pgsize, PROT_READ | PROT_WRITE, - MAP_ANON | MAP_PRIVATE, -1, 0); - if (p == MAP_FAILED) - goto unlock; - p->ind = 1; - p->max = (pgsize - ((char *)&p->fns[0] - (char *)p)) / - sizeof(p->fns[0]); - p->next = NULL; - __atexit = p; - } else { - if (mprotect(p, pgsize, PROT_READ | PROT_WRITE)) - goto unlock; - } - p->fns[0].fn_ptr = (void (*)(void *))func; - p->fns[0].fn_arg = NULL; - p->fns[0].fn_dso = NULL; - mprotect(p, pgsize, PROT_READ); - restartloop = 1; -unlock: - _ATEXIT_UNLOCK(); -} diff --git a/libc/upstream-openbsd/lib/libc/stdlib/atexit.h b/libc/upstream-openbsd/lib/libc/stdlib/atexit.h deleted file mode 100644 index 3de2aa3..0000000 --- a/libc/upstream-openbsd/lib/libc/stdlib/atexit.h +++ /dev/null @@ -1,47 +0,0 @@ -/* $OpenBSD: atexit.h,v 1.9 2014/06/18 19:01:10 kettenis Exp $ */ - -/* - * Copyright (c) 2002 Daniel Hartmeier - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - */ - -struct atexit { - struct atexit *next; /* next in list */ - int ind; /* next index in this table */ - int max; /* max entries >= ATEXIT_SIZE */ - struct atexit_fn { - void (*fn_ptr)(void *); - void *fn_arg; /* argument for CXA callback */ - void *fn_dso; /* shared module handle */ - } fns[1]; /* the table itself */ -}; - -extern struct atexit *__atexit; /* points to head of LIFO stack */ - -int __cxa_atexit(void (*)(void *), void *, void *); -void __cxa_finalize(void *); diff --git a/tests/libs/Android.build.pthread_atfork.mk b/tests/libs/Android.build.pthread_atfork.mk new file mode 100644 index 0000000..72ffec4 --- /dev/null +++ b/tests/libs/Android.build.pthread_atfork.mk @@ -0,0 +1,25 @@ +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# ----------------------------------------------------------------------------- +# This library used to test phtread_atfork handler behaviour +# during/after dlclose. +# ----------------------------------------------------------------------------- +libtest_pthread_atfork_src_files := pthread_atfork.cpp + +module := libtest_pthread_atfork +include $(LOCAL_PATH)/Android.build.testlib.mk + diff --git a/tests/libs/Android.mk b/tests/libs/Android.mk index cd6709a..dfebdd8 100644 --- a/tests/libs/Android.mk +++ b/tests/libs/Android.mk @@ -25,6 +25,7 @@ common_additional_dependencies := \ $(LOCAL_PATH)/Android.build.dlopen_check_order_dlsym.mk \ $(LOCAL_PATH)/Android.build.dlopen_check_order_reloc_siblings.mk \ $(LOCAL_PATH)/Android.build.dlopen_check_order_reloc_main_executable.mk \ + $(LOCAL_PATH)/Android.build.pthread_atfork.mk \ $(LOCAL_PATH)/Android.build.testlib.mk \ $(LOCAL_PATH)/Android.build.versioned_lib.mk \ $(TEST_PATH)/Android.build.mk @@ -204,6 +205,11 @@ include $(LOCAL_PATH)/Android.build.dlopen_check_order_reloc_main_executable.mk include $(LOCAL_PATH)/Android.build.versioned_lib.mk # ----------------------------------------------------------------------------- +# Build libraries needed by pthread_atfork tests +# ----------------------------------------------------------------------------- +include $(LOCAL_PATH)/Android.build.pthread_atfork.mk + +# ----------------------------------------------------------------------------- # Library with dependency loop used by dlfcn tests # # libtest_with_dependency_loop -> a -> b -> c -> a diff --git a/tests/libs/pthread_atfork.cpp b/tests/libs/pthread_atfork.cpp new file mode 100644 index 0000000..3a5aa4f --- /dev/null +++ b/tests/libs/pthread_atfork.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +extern "C" int proxy_pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void)) { + return pthread_atfork(prepare, parent, child); +} diff --git a/tests/pthread_test.cpp b/tests/pthread_test.cpp index a299f02..b8cfd56 100644 --- a/tests/pthread_test.cpp +++ b/tests/pthread_test.cpp @@ -16,6 +16,7 @@ #include +#include #include #include #include @@ -987,14 +988,14 @@ TEST(pthread, pthread_once_1934122) { } static int g_atfork_prepare_calls = 0; -static void AtForkPrepare1() { g_atfork_prepare_calls = (g_atfork_prepare_calls << 4) | 1; } -static void AtForkPrepare2() { g_atfork_prepare_calls = (g_atfork_prepare_calls << 4) | 2; } +static void AtForkPrepare1() { g_atfork_prepare_calls = (g_atfork_prepare_calls * 10) + 1; } +static void AtForkPrepare2() { g_atfork_prepare_calls = (g_atfork_prepare_calls * 10) + 2; } static int g_atfork_parent_calls = 0; -static void AtForkParent1() { g_atfork_parent_calls = (g_atfork_parent_calls << 4) | 1; } -static void AtForkParent2() { g_atfork_parent_calls = (g_atfork_parent_calls << 4) | 2; } +static void AtForkParent1() { g_atfork_parent_calls = (g_atfork_parent_calls * 10) + 1; } +static void AtForkParent2() { g_atfork_parent_calls = (g_atfork_parent_calls * 10) + 2; } static int g_atfork_child_calls = 0; -static void AtForkChild1() { g_atfork_child_calls = (g_atfork_child_calls << 4) | 1; } -static void AtForkChild2() { g_atfork_child_calls = (g_atfork_child_calls << 4) | 2; } +static void AtForkChild1() { g_atfork_child_calls = (g_atfork_child_calls * 10) + 1; } +static void AtForkChild2() { g_atfork_child_calls = (g_atfork_child_calls * 10) + 2; } TEST(pthread, pthread_atfork_smoke) { ASSERT_EQ(0, pthread_atfork(AtForkPrepare1, AtForkParent1, AtForkChild1)); @@ -1005,13 +1006,71 @@ TEST(pthread, pthread_atfork_smoke) { // Child and parent calls are made in the order they were registered. if (pid == 0) { - ASSERT_EQ(0x12, g_atfork_child_calls); + ASSERT_EQ(12, g_atfork_child_calls); _exit(0); } - ASSERT_EQ(0x12, g_atfork_parent_calls); + ASSERT_EQ(12, g_atfork_parent_calls); // Prepare calls are made in the reverse order. - ASSERT_EQ(0x21, g_atfork_prepare_calls); + ASSERT_EQ(21, g_atfork_prepare_calls); + int status; + ASSERT_EQ(pid, waitpid(pid, &status, 0)); +} + +static void AtForkPrepare3() { g_atfork_prepare_calls = (g_atfork_prepare_calls * 10) + 3; } +static void AtForkPrepare4() { g_atfork_prepare_calls = (g_atfork_prepare_calls * 10) + 4; } + +static void AtForkParent3() { g_atfork_parent_calls = (g_atfork_parent_calls * 10) + 3; } +static void AtForkParent4() { g_atfork_parent_calls = (g_atfork_parent_calls * 10) + 4; } + +static void AtForkChild3() { g_atfork_child_calls = (g_atfork_child_calls * 10) + 3; } +static void AtForkChild4() { g_atfork_child_calls = (g_atfork_child_calls * 10) + 4; } + +TEST(pthread, pthread_atfork_with_dlclose) { + ASSERT_EQ(0, pthread_atfork(AtForkPrepare1, AtForkParent1, AtForkChild1)); + + void* handle = dlopen("libtest_pthread_atfork.so", RTLD_NOW | RTLD_LOCAL); + ASSERT_TRUE(handle != nullptr) << dlerror(); + typedef int (*fn_t)(void (*)(void), void (*)(void), void (*)(void)); + fn_t fn = reinterpret_cast(dlsym(handle, "proxy_pthread_atfork")); + ASSERT_TRUE(fn != nullptr) << dlerror(); + // the library registers 2 additional atfork handlers in a constructor + ASSERT_EQ(0, fn(AtForkPrepare2, AtForkParent2, AtForkChild2)); + ASSERT_EQ(0, fn(AtForkPrepare3, AtForkParent3, AtForkChild3)); + + ASSERT_EQ(0, pthread_atfork(AtForkPrepare4, AtForkParent4, AtForkChild4)); + + int pid = fork(); + + ASSERT_NE(-1, pid) << strerror(errno); + + if (pid == 0) { + ASSERT_EQ(1234, g_atfork_child_calls); + _exit(0); + } + + ASSERT_EQ(1234, g_atfork_parent_calls); + ASSERT_EQ(4321, g_atfork_prepare_calls); + + EXPECT_EQ(0, dlclose(handle)); + g_atfork_prepare_calls = g_atfork_parent_calls = g_atfork_child_calls = 0; + + int status; + ASSERT_EQ(pid, waitpid(pid, &status, 0)); + + pid = fork(); + + ASSERT_NE(-1, pid) << strerror(errno); + + if (pid == 0) { + ASSERT_EQ(14, g_atfork_child_calls); + _exit(0); + } + + ASSERT_EQ(14, g_atfork_parent_calls); + ASSERT_EQ(41, g_atfork_prepare_calls); + + ASSERT_EQ(pid, waitpid(pid, &status, 0)); } TEST(pthread, pthread_attr_getscope) { -- cgit v1.1