From 990c6d9cb6f0176ba8acfe0cbe92facc819b7a90 Mon Sep 17 00:00:00 2001 From: Joshua Peraza Date: Fri, 22 Dec 2017 15:32:37 -0800 Subject: [PATCH] android: add Dlsym() which wraps `dlsym` `dlsym` on Android KitKat (4.4.*) raises SIGFPE when searching for non-existent symbols. This wrapper installs a signal handler prior to calling `dlsym`. Bug: crashpad:30 Change-Id: Iee94672d3c11b1fad1b01526eea7df688c0356cb Reviewed-on: https://chromium-review.googlesource.com/835411 Commit-Queue: Joshua Peraza Reviewed-by: Mark Mentovai --- compat/BUILD.gn | 2 + compat/android/dlfcn_internal.cc | 166 +++++++++++++++++++++++++++++++ compat/android/dlfcn_internal.h | 35 +++++++ compat/android/sys/epoll.cc | 13 ++- compat/android/sys/mman.cc | 6 +- compat/compat.gyp | 2 + 6 files changed, 215 insertions(+), 9 deletions(-) create mode 100644 compat/android/dlfcn_internal.cc create mode 100644 compat/android/dlfcn_internal.h diff --git a/compat/BUILD.gn b/compat/BUILD.gn index 6c81e70f..8db7b4de 100644 --- a/compat/BUILD.gn +++ b/compat/BUILD.gn @@ -74,6 +74,8 @@ compat_target("compat") { if (crashpad_is_android) { sources += [ + "android/dlfcn_internal.cc", + "android/dlfcn_internal.h", "android/elf.h", "android/linux/elf.h", "android/linux/prctl.h", diff --git a/compat/android/dlfcn_internal.cc b/compat/android/dlfcn_internal.cc new file mode 100644 index 00000000..5d98fe84 --- /dev/null +++ b/compat/android/dlfcn_internal.cc @@ -0,0 +1,166 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// 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 "dlfcn_internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace crashpad { +namespace internal { + +// KitKat supports API levels up to 20. +#if __ANDROID_API__ < 21 + +namespace { + +class ScopedSigactionRestore { + public: + ScopedSigactionRestore() : old_action_(), signo_(-1), valid_(false) {} + + ~ScopedSigactionRestore() { Reset(); } + + bool Reset() { + bool result = true; + if (valid_) { + result = sigaction(signo_, &old_action_, nullptr) == 0; + if (!result) { + PrintErrmsg(errno); + } + } + valid_ = false; + signo_ = -1; + return result; + } + + bool ResetAndInstallHandler(int signo, + void (*handler)(int, siginfo_t*, void*)) { + Reset(); + + struct sigaction act; + act.sa_sigaction = handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + if (sigaction(signo, &act, &old_action_) != 0) { + PrintErrmsg(errno); + return false; + } + signo_ = signo; + valid_ = true; + return true; + } + + private: + void PrintErrmsg(int err) { + char errmsg[256]; + + if (strerror_r(err, errmsg, sizeof(errmsg)) != 0) { + snprintf(errmsg, + sizeof(errmsg), + "%s:%d: Couldn't set errmsg for %d: %d", + __FILE__, + __LINE__, + err, + errno); + return; + } + + fprintf(stderr, "%s:%d: sigaction: %s", __FILE__, __LINE__, errmsg); + } + + struct sigaction old_action_; + int signo_; + bool valid_; +}; + +bool IsKitKat() { + char prop_buf[PROP_VALUE_MAX]; + int length = __system_property_get("ro.build.version.sdk", prop_buf); + if (length <= 0) { + fprintf(stderr, "%s:%d: Couldn't get version", __FILE__, __LINE__); + // It's safer to assume this is KitKat and execute dlsym with a signal + // handler installed. + return true; + } + if (strcmp(prop_buf, "19") == 0 || strcmp(prop_buf, "20") == 0) { + return true; + } + return false; +} + +class ScopedSetTID { + public: + explicit ScopedSetTID(pid_t* tid) : tid_(tid) { *tid_ = syscall(SYS_gettid); } + + ~ScopedSetTID() { *tid_ = -1; } + + private: + pid_t* tid_; +}; + +sigjmp_buf dlsym_sigjmp_env; + +pid_t dlsym_tid = -1; + +void HandleSIGFPE(int signo, siginfo_t* siginfo, void* context) { + if (siginfo->si_code != FPE_INTDIV || syscall(SYS_gettid) != dlsym_tid) { + return; + } + siglongjmp(dlsym_sigjmp_env, 1); +} + +} // namespace + +void* Dlsym(void* handle, const char* symbol) { + if (!IsKitKat()) { + return dlsym(handle, symbol); + } + + static std::mutex* signal_handler_mutex = new std::mutex(); + std::lock_guard lock(*signal_handler_mutex); + + ScopedSetTID set_tid(&dlsym_tid); + + ScopedSigactionRestore sig_restore; + if (!sig_restore.ResetAndInstallHandler(SIGFPE, HandleSIGFPE)) { + return nullptr; + } + + if (sigsetjmp(dlsym_sigjmp_env, 1) != 0) { + return nullptr; + } + + return dlsym(handle, symbol); +} + +#else + +void* Dlsym(void* handle, const char* symbol) { + return dlsym(handle, symbol); +} + +#endif // __ANDROID_API__ < 21 + +} // namespace internal +} // namespace crashpad diff --git a/compat/android/dlfcn_internal.h b/compat/android/dlfcn_internal.h new file mode 100644 index 00000000..ed4083db --- /dev/null +++ b/compat/android/dlfcn_internal.h @@ -0,0 +1,35 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// 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. + +#ifndef CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_ +#define CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_ + +namespace crashpad { +namespace internal { + +//! \brief Provide a wrapper for `dlsym`. +//! +//! dlsym on Android KitKat (4.4.*) raises SIGFPE when searching for a +//! non-existent symbol. This wrapper avoids crashing in this circumstance. +//! https://code.google.com/p/android/issues/detail?id=61799 +//! +//! The parameters and return value for this function are the same as for +//! `dlsym`, but a return value for `dlerror` may not be set in the event of an +//! error. +void* Dlsym(void* handle, const char* symbol); + +} // namespace internal +} // namespace crashpad + +#endif // CRASHPAD_COMPAT_ANDROID_DLFCN_INTERNAL_H_ diff --git a/compat/android/sys/epoll.cc b/compat/android/sys/epoll.cc index 64de763f..7fd3bb37 100644 --- a/compat/android/sys/epoll.cc +++ b/compat/android/sys/epoll.cc @@ -18,18 +18,17 @@ #include #include +#include "dlfcn_internal.h" + #if __ANDROID_API__ < 21 extern "C" { int epoll_create1(int flags) { - static const auto epoll_create1_p = - reinterpret_cast(dlsym(RTLD_DEFAULT, "epoll_create1")); - if (epoll_create1_p) { - return epoll_create1_p(flags); - } - - return syscall(SYS_epoll_create1, flags); + static const auto epoll_create1_p = reinterpret_cast( + crashpad::internal::Dlsym(RTLD_DEFAULT, "epoll_create1")); + return epoll_create1_p ? epoll_create1_p(flags) + : syscall(SYS_epoll_create1, flags); } } // extern "C" diff --git a/compat/android/sys/mman.cc b/compat/android/sys/mman.cc index f4d722c9..4c29a9df 100644 --- a/compat/android/sys/mman.cc +++ b/compat/android/sys/mman.cc @@ -19,6 +19,8 @@ #include #include +#include "dlfcn_internal.h" + #if defined(__USE_FILE_OFFSET64) && __ANDROID_API__ < 21 // Bionic has provided a wrapper for __mmap2() since the beginning of time. See @@ -87,8 +89,8 @@ void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) { // Use the system’s mmap64() wrapper if available. It will be available on // Android 5.0 (“Lollipop”) and later. using Mmap64Type = void* (*)(void*, size_t, int, int, int, off64_t); - static const Mmap64Type mmap64 = - reinterpret_cast(dlsym(RTLD_DEFAULT, "mmap64")); + static const Mmap64Type mmap64 = reinterpret_cast( + crashpad::internal::Dlsym(RTLD_DEFAULT, "mmap64")); if (mmap64) { return mmap64(addr, size, prot, flags, fd, offset); } diff --git a/compat/compat.gyp b/compat/compat.gyp index d3b785b9..c5fe350f 100644 --- a/compat/compat.gyp +++ b/compat/compat.gyp @@ -21,6 +21,8 @@ 'target_name': 'crashpad_compat', 'type': 'static_library', 'sources': [ + 'android/dlfcn_internal.cc', + 'android/dlfcn_internal.h', 'android/elf.h', 'android/linux/elf.h', 'android/linux/prctl.h',