diff options
Diffstat (limited to 'mozglue/interposers')
-rw-r--r-- | mozglue/interposers/InterposerHelper.h | 79 | ||||
-rw-r--r-- | mozglue/interposers/env_interposer.cpp | 79 | ||||
-rw-r--r-- | mozglue/interposers/getline_interposer.cpp | 110 | ||||
-rw-r--r-- | mozglue/interposers/moz.build | 25 | ||||
-rw-r--r-- | mozglue/interposers/pthread_create_interposer.cpp | 108 |
5 files changed, 401 insertions, 0 deletions
diff --git a/mozglue/interposers/InterposerHelper.h b/mozglue/interposers/InterposerHelper.h new file mode 100644 index 0000000000..58e55d081e --- /dev/null +++ b/mozglue/interposers/InterposerHelper.h @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef InterposerHelper_h +#define InterposerHelper_h + +#include <type_traits> + +#ifdef MOZ_LINKER +# include "Linker.h" +#else +# include <dlfcn.h> +#endif + +#include "mozilla/Assertions.h" + +template <typename T> +static inline T dlsym_wrapper(void* aHandle, const char* aName) { +#ifdef MOZ_LINKER + return reinterpret_cast<T>(__wrap_dlsym(aHandle, aName)); +#else + return reinterpret_cast<T>(dlsym(aHandle, aName)); +#endif // MOZ_LINKER +} + +static inline void* dlopen_wrapper(const char* aPath, int flags) { +#ifdef MOZ_LINKER + return __wrap_dlopen(aPath, flags); +#else + return dlopen(aPath, flags); +#endif // MOZ_LINKER +} + +template <typename T> +static T get_real_symbol(const char* aName, T aReplacementSymbol) { + // T can only be a function pointer + static_assert(std::is_function<typename std::remove_pointer<T>::type>::value); + + // Find the corresponding function in the linked libraries + T real_symbol = dlsym_wrapper<T>(RTLD_NEXT, aName); + +#if defined(ANDROID) + if ((real_symbol == nullptr) || (real_symbol == aReplacementSymbol)) { + // On old versions of Android the application runtime links in libc before + // we get a chance to link libmozglue, so its symbols don't appear when + // resolving them with RTLD_NEXT. This behavior differs between the + // different versions of Android so we'll just look for them directly into + // libc.so. Note that this won't work if we're trying to interpose + // functions that are in other libraries, but hopefully we'll never have + // to do that. + void* handle = dlopen_wrapper("libc.so", RTLD_LAZY); + + if (handle) { + real_symbol = dlsym_wrapper<T>(handle, aName); + } + } +#endif + + if (real_symbol == nullptr) { + MOZ_CRASH_UNSAFE_PRINTF( + "%s() interposition failed but the interposer function is " + "still being called, this won't work!", + aName); + } + + if (real_symbol == aReplacementSymbol) { + MOZ_CRASH_UNSAFE_PRINTF( + "We could not obtain the real %s(). Calling the symbol we " + "got would make us enter an infinite loop so stop here instead.", + aName); + } + + return real_symbol; +} + +#define GET_REAL_SYMBOL(name) get_real_symbol(#name, name) + +#endif // InterposerHelper_h diff --git a/mozglue/interposers/env_interposer.cpp b/mozglue/interposers/env_interposer.cpp new file mode 100644 index 0000000000..d8c11b5d35 --- /dev/null +++ b/mozglue/interposers/env_interposer.cpp @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <pthread.h> +#include <string.h> +#include <unistd.h> + +#include "InterposerHelper.h" + +// The interposers in this file cover all the functions used to access the +// environment (getenv(), putenv(), setenv(), unsetenv() and clearenv()). They +// all use the mutex below for synchronization to prevent races that caused +// startup crashes, see bug 1752703. +static pthread_mutex_t gEnvLock = PTHREAD_MUTEX_INITIALIZER; + +static char* internal_getenv(const char* aName) { + if (environ == nullptr || aName[0] == '\0') { + return nullptr; + } + + size_t len = strlen(aName); + for (char** env_ptr = environ; *env_ptr != nullptr; ++env_ptr) { + if ((aName[0] == (*env_ptr)[0]) && (strncmp(aName, *env_ptr, len) == 0) && + ((*env_ptr)[len] == '=')) { + return *env_ptr + len + 1; + } + } + + return nullptr; +} + +extern "C" { + +MFBT_API char* getenv(const char* name) { + pthread_mutex_lock(&gEnvLock); + char* result = internal_getenv(name); + pthread_mutex_unlock(&gEnvLock); + + return result; +} + +MFBT_API int putenv(char* string) { + static const auto real_putenv = GET_REAL_SYMBOL(putenv); + + pthread_mutex_lock(&gEnvLock); + int result = real_putenv(string); + pthread_mutex_unlock(&gEnvLock); + return result; +} + +MFBT_API int setenv(const char* name, const char* value, int replace) { + static const auto real_setenv = GET_REAL_SYMBOL(setenv); + + pthread_mutex_lock(&gEnvLock); + int result = real_setenv(name, value, replace); + pthread_mutex_unlock(&gEnvLock); + return result; +} + +MFBT_API int unsetenv(const char* name) { + static const auto real_unsetenv = GET_REAL_SYMBOL(unsetenv); + + pthread_mutex_lock(&gEnvLock); + int result = real_unsetenv(name); + pthread_mutex_unlock(&gEnvLock); + return result; +} + +MFBT_API int clearenv(void) { + static const auto real_clearenv = GET_REAL_SYMBOL(clearenv); + + pthread_mutex_lock(&gEnvLock); + int result = real_clearenv(); + pthread_mutex_unlock(&gEnvLock); + return result; +} + +} // extern "C" diff --git a/mozglue/interposers/getline_interposer.cpp b/mozglue/interposers/getline_interposer.cpp new file mode 100644 index 0000000000..902b52b718 --- /dev/null +++ b/mozglue/interposers/getline_interposer.cpp @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Interposing getline because of + * https://bugzilla.mozilla.org/show_bug.cgi?id=914190 + */ + +#ifdef __ANDROID__ + +# include <cstdlib> +# include <cstdio> +# include <cerrno> + +# include <limits> + +namespace { + +// RAII on file locking. +class FileLocker { + FILE* stream; + + public: + explicit FileLocker(FILE* stream) : stream(stream) { flockfile(stream); } + ~FileLocker() { funlockfile(stream); } +}; + +ssize_t internal_getdelim(char** __restrict lineptr, size_t* __restrict n, + int delim, FILE* __restrict stream) { + constexpr size_t n_default = 64; + constexpr size_t n_max = + std::numeric_limits<ssize_t>::max() < std::numeric_limits<size_t>::max() + ? (size_t)std::numeric_limits<ssize_t>::max() + 1 + : std::numeric_limits<size_t>::max(); + constexpr size_t n_limit = 2 * ((n_max - 1) / 3); + + if (!lineptr || !n || !stream) { + errno = EINVAL; + return -1; + } + + // Lock the file so that we can us unlocked getc in the inner loop. + FileLocker fl(stream); + + if (!*lineptr || *n == 0) { + *n = n_default; + if (auto* new_lineptr = reinterpret_cast<char*>(realloc(*lineptr, *n))) { + *lineptr = new_lineptr; + } else { + errno = ENOMEM; + return -1; + } + } + + ssize_t result; + size_t cur_len = 0; + + while (true) { + // Retrieve an extra char. + int i = getc_unlocked(stream); + if (i == EOF) { + result = -1; + break; + } + + // Eventually grow the buffer. + if (cur_len + 1 >= *n) { + size_t needed = *n >= n_limit ? n_max : 3 * *n / 2 + 1; + + if (cur_len + 1 >= needed) { + errno = EOVERFLOW; + return -1; + } + + if (auto* new_lineptr = (char*)realloc(*lineptr, needed)) { + *lineptr = new_lineptr; + } else { + errno = ENOMEM; + return -1; + } + *n = needed; + } + + (*lineptr)[cur_len] = i; + cur_len++; + + if (i == delim) break; + } + (*lineptr)[cur_len] = '\0'; + return cur_len ? cur_len : result; +} + +} // namespace + +extern "C" { + +MFBT_API ssize_t getline(char** __restrict lineptr, size_t* __restrict n, + FILE* __restrict stream) { + return internal_getdelim(lineptr, n, '\n', stream); +} + +MFBT_API ssize_t getdelim(char** __restrict lineptr, size_t* __restrict n, + int delim, FILE* __restrict stream) { + return internal_getdelim(lineptr, n, delim, stream); +} + +} // extern "C" + +#endif diff --git a/mozglue/interposers/moz.build b/mozglue/interposers/moz.build new file mode 100644 index 0000000000..48e88946b5 --- /dev/null +++ b/mozglue/interposers/moz.build @@ -0,0 +1,25 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +Library("interposers") + +DEFINES["IMPL_MFBT"] = True + +UNIFIED_SOURCES += [ + "env_interposer.cpp", + "getline_interposer.cpp", +] + +if CONFIG["MOZ_CRASHREPORTER"]: + UNIFIED_SOURCES += [ + "pthread_create_interposer.cpp", + ] + +if CONFIG["MOZ_LINKER"]: + LOCAL_INCLUDES += [ + "/mozglue/linker", + ] + +FINAL_LIBRARY = "mozglue" diff --git a/mozglue/interposers/pthread_create_interposer.cpp b/mozglue/interposers/pthread_create_interposer.cpp new file mode 100644 index 0000000000..65f60c2d1b --- /dev/null +++ b/mozglue/interposers/pthread_create_interposer.cpp @@ -0,0 +1,108 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> + +#include <pthread.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/mman.h> + +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" + +#include "InterposerHelper.h" + +using mozilla::DebugOnly; + +struct SigAltStack { + void* mem; + size_t size; +}; + +struct PthreadCreateParams { + void* (*start_routine)(void*); + void* arg; +}; + +// Install the alternate signal stack, returns a pointer to the memory area we +// mapped to store the stack only if it was installed successfully, otherwise +// returns NULL. +static void* install_sig_alt_stack(size_t size) { + void* alt_stack_mem = mmap(nullptr, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (alt_stack_mem) { + stack_t alt_stack = { + .ss_sp = alt_stack_mem, + .ss_flags = 0, + .ss_size = size, + }; + + int rv = sigaltstack(&alt_stack, nullptr); + if (rv == 0) { + return alt_stack_mem; + } + + rv = munmap(alt_stack_mem, size); + MOZ_ASSERT(rv == 0); + } + + return nullptr; +} + +// Uninstall the alternate signal handler and unmaps it. Does nothing if +// alt_stack_mem is NULL. +static void uninstall_sig_alt_stack(void* alt_stack_ptr) { + SigAltStack* alt_stack = static_cast<SigAltStack*>(alt_stack_ptr); + if (alt_stack->mem) { + stack_t disable_alt_stack = {}; + disable_alt_stack.ss_flags = SS_DISABLE; + DebugOnly<int> rv = sigaltstack(&disable_alt_stack, nullptr); + MOZ_ASSERT(rv == 0); + rv = munmap(alt_stack->mem, alt_stack->size); + MOZ_ASSERT(rv == 0); + } +} + +// This replaces the routine passed to pthread_create() when a thread is +// started, it handles the alternate signal stack and calls the thread's +// actual routine. +void* set_alt_signal_stack_and_start(PthreadCreateParams* params) { + void* (*start_routine)(void*) = params->start_routine; + void* arg = params->arg; + free(params); + + void* thread_rv = nullptr; + static const size_t kSigStackSize = std::max(size_t(16384), size_t(SIGSTKSZ)); + void* alt_stack_mem = install_sig_alt_stack(kSigStackSize); + SigAltStack alt_stack{alt_stack_mem, kSigStackSize}; + pthread_cleanup_push(uninstall_sig_alt_stack, &alt_stack); + thread_rv = start_routine(arg); + pthread_cleanup_pop(1); + + return thread_rv; +} + +extern "C" { +// This interposer replaces libpthread's pthread_create() so that we can +// inject an alternate signal stack in every new thread. +MFBT_API int pthread_create(pthread_t* thread, const pthread_attr_t* attr, + void* (*start_routine)(void*), void* arg) { + static const auto real_pthread_create = GET_REAL_SYMBOL(pthread_create); + + PthreadCreateParams* params = + (PthreadCreateParams*)malloc(sizeof(PthreadCreateParams)); + params->start_routine = start_routine; + params->arg = arg; + + int result = real_pthread_create( + thread, attr, (void* (*)(void*))set_alt_signal_stack_and_start, params); + + if (result != 0) { + free(params); + } + + return result; +} +} |