From f4b80bcc7190fe75023b983d56a795b440a0c515 Mon Sep 17 00:00:00 2001 From: Dave Allison Date: Wed, 14 May 2014 15:41:25 -0700 Subject: [PATCH] Make use of sigchainlib for signal chaining for implicit checks This adds a preload library that overrides sigaction and sigprocmask to implement signal chaining. Signal chaining allows us to chain any signal so that the ART runtime receives it before any signal handler registered in native code by an application. If the ART signal handler doesn't want it, it will pass it on to the user's handler. ART uses signals for null pointer checks, stack overflow checks and suspend points. Also adds an OAT test to test this in isolation. Change-Id: I9545f9f7343774c091410eb810504d9855fd399f --- Android.mk | 2 + build/Android.libarttest.mk | 1 + runtime/Android.mk | 5 +- runtime/fault_handler.cc | 27 +++++- runtime/runtime.cc | 17 +++- runtime/thread.h | 2 +- sigchainlib/Android.mk | 29 +++++++ sigchainlib/sigchain.cc | 183 ++++++++++++++++++++++++++++++++++++++++ sigchainlib/sigchain.h | 29 +++++++ test/Android.mk | 1 + test/SignalTest/SignalTest.java | 60 +++++++++++++ test/SignalTest/signaltest.cc | 74 ++++++++++++++++ 12 files changed, 424 insertions(+), 6 deletions(-) create mode 100644 sigchainlib/Android.mk create mode 100644 sigchainlib/sigchain.cc create mode 100644 sigchainlib/sigchain.h create mode 100644 test/SignalTest/SignalTest.java create mode 100644 test/SignalTest/signaltest.cc diff --git a/Android.mk b/Android.mk index b329a5a4e..a30c09002 100644 --- a/Android.mk +++ b/Android.mk @@ -90,6 +90,8 @@ include $(art_path)/oatdump/Android.mk include $(art_path)/dalvikvm/Android.mk include $(art_path)/tools/Android.mk include $(art_build_path)/Android.oat.mk +include $(art_path)/sigchainlib/Android.mk + diff --git a/build/Android.libarttest.mk b/build/Android.libarttest.mk index 9e5f3d6e4..c08092895 100644 --- a/build/Android.libarttest.mk +++ b/build/Android.libarttest.mk @@ -16,6 +16,7 @@ LIBARTTEST_COMMON_SRC_FILES := \ test/JniTest/jni_test.cc \ + test/SignalTest/signaltest.cc \ test/ReferenceMap/stack_walk_refmap_jni.cc \ test/StackWalk/stack_walk_jni.cc \ test/UnsafeTest/unsafe_test.cc diff --git a/runtime/Android.mk b/runtime/Android.mk index c2507b145..a0648b0ff 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -402,11 +402,13 @@ $$(ENUM_OPERATOR_OUT_GEN): $$(GENERATED_SRC_DIR)/%_operator_out.cc : $(LOCAL_PAT endif endif LOCAL_C_INCLUDES += $(ART_C_INCLUDES) + LOCAL_C_INCLUDES += art/sigchainlib + LOCAL_SHARED_LIBRARIES += liblog libnativehelper include external/libcxx/libcxx.mk LOCAL_SHARED_LIBRARIES += libbacktrace_libc++ ifeq ($$(art_target_or_host),target) - LOCAL_SHARED_LIBRARIES += libcutils libdl libselinux libutils + LOCAL_SHARED_LIBRARIES += libcutils libdl libselinux libutils libsigchain LOCAL_STATIC_LIBRARIES := libziparchive libz else # host LOCAL_STATIC_LIBRARIES += libcutils libziparchive-host libz libutils @@ -459,3 +461,4 @@ endif ifeq ($(ART_BUILD_TARGET_DEBUG),true) $(eval $(call build-libart,target,debug,$(ART_TARGET_CLANG))) endif + diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc index 8d750c5bd..15c38c1f4 100644 --- a/runtime/fault_handler.cc +++ b/runtime/fault_handler.cc @@ -29,12 +29,22 @@ #include "mirror/object-inl.h" #include "object_utils.h" #include "scoped_thread_state_change.h" +#ifdef HAVE_ANDROID_OS +#include "sigchain.h" +#endif #include "verify_object-inl.h" namespace art { // Static fault manger object accessed by signal handler. FaultManager fault_manager; +extern "C" { +void art_sigsegv_fault() { + // Set a breakpoint here to be informed when a SIGSEGV is unhandled by ART. + VLOG(signals)<< "Caught unknown SIGSEGV in ART fault handler - chaining to next handler."; +} +} + // Signal handler called on SIGSEGV. static void art_fault_handler(int sig, siginfo_t* info, void* context) { fault_manager.HandleFault(sig, info, context); @@ -45,9 +55,13 @@ FaultManager::FaultManager() { } FaultManager::~FaultManager() { +#ifdef HAVE_ANDROID_OS + UnclaimSignalChain(SIGSEGV); +#endif sigaction(SIGSEGV, &oldaction_, nullptr); // Restore old handler. } + void FaultManager::Init() { struct sigaction action; action.sa_sigaction = art_fault_handler; @@ -56,7 +70,13 @@ void FaultManager::Init() { #if !defined(__mips__) action.sa_restorer = nullptr; #endif + + // Set our signal handler now. sigaction(SIGSEGV, &action, &oldaction_); +#ifdef HAVE_ANDROID_OS + // Make sure our signal handler is called before any user handlers. + ClaimSignalChain(SIGSEGV, &oldaction_); +#endif } void FaultManager::HandleFault(int sig, siginfo_t* info, void* context) { @@ -79,8 +99,13 @@ void FaultManager::HandleFault(int sig, siginfo_t* info, void* context) { return; } } - VLOG(signals)<< "Caught unknown SIGSEGV in ART fault handler"; + art_sigsegv_fault(); + +#ifdef HAVE_ANDROID_OS + InvokeUserSignalHandler(sig, info, context); +#else oldaction_.sa_sigaction(sig, info, context); +#endif } void FaultManager::AddHandler(FaultHandler* handler, bool generated_code) { diff --git a/runtime/runtime.cc b/runtime/runtime.cc index 23a49cb33..361070c02 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -562,9 +562,20 @@ bool Runtime::Init(const Options& raw_options, bool ignore_unrecognized) { GetInstrumentation()->ForceInterpretOnly(); } - if (options->explicit_checks_ != (ParsedOptions::kExplicitSuspendCheck | - ParsedOptions::kExplicitNullCheck | - ParsedOptions::kExplicitStackOverflowCheck) || kEnableJavaStackTraceHandler) { + bool implicit_checks_supported = false; + switch (kRuntimeISA) { + case kArm: + case kThumb2: + implicit_checks_supported = true; + break; + default: + break; + } + + if (implicit_checks_supported && + (options->explicit_checks_ != (ParsedOptions::kExplicitSuspendCheck | + ParsedOptions::kExplicitNullCheck | + ParsedOptions::kExplicitStackOverflowCheck) || kEnableJavaStackTraceHandler)) { fault_manager.Init(); // These need to be in a specific order. The null point check handler must be diff --git a/runtime/thread.h b/runtime/thread.h index 9a7cb486d..08bbcaec4 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -121,7 +121,7 @@ class Thread { // of the stack (lowest memory). The higher portion of the memory // is protected against reads and the lower is available for use while // throwing the StackOverflow exception. - static constexpr size_t kStackOverflowProtectedSize = 32 * KB; + static constexpr size_t kStackOverflowProtectedSize = 16 * KB; static constexpr size_t kStackOverflowImplicitCheckSize = kStackOverflowProtectedSize + kStackOverflowReservedBytes; diff --git a/sigchainlib/Android.mk b/sigchainlib/Android.mk new file mode 100644 index 000000000..cb1778dc8 --- /dev/null +++ b/sigchainlib/Android.mk @@ -0,0 +1,29 @@ +# +# 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. +# + +LOCAL_PATH:= $(call my-dir) + +include art/build/Android.common.mk + +include $(CLEAR_VARS) +LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION) +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS += $(ART_TARGET_CFLAGS) +LOCAL_SRC_FILES := sigchain.cc +LOCAL_MODULE:= libsigchain +LOCAL_SHARED_LIBRARIES += liblog libdl +LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk +include $(BUILD_SHARED_LIBRARY) diff --git a/sigchainlib/sigchain.cc b/sigchainlib/sigchain.cc new file mode 100644 index 000000000..26e7d319c --- /dev/null +++ b/sigchainlib/sigchain.cc @@ -0,0 +1,183 @@ +/* + * 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 +#include +#include +#include +#include + +namespace art { + +class SignalAction { + public: + SignalAction() : claimed_(false) { + } + + // Claim the signal and keep the action specified. + void Claim(const struct sigaction& action) { + action_ = action; + claimed_ = true; + } + + // Unclaim the signal and restore the old action. + void Unclaim(int signal) { + claimed_ = false; + sigaction(signal, &action_, NULL); // Restore old action. + } + + // Get the action associated with this signal. + const struct sigaction& GetAction() const { + return action_; + } + + // Is the signal claimed? + bool IsClaimed() const { + return claimed_; + } + + // Change the recorded action to that specified. + void SetAction(const struct sigaction& action) { + action_ = action; + } + + private: + struct sigaction action_; // Action to be performed. + bool claimed_; // Whether signal is claimed or not. +}; + +// User's signal handlers +static SignalAction user_sigactions[_NSIG]; + +static void log(const char* format, ...) { + char buf[256]; + va_list ap; + va_start(ap, format); + vsnprintf(buf, sizeof(buf), format, ap); + __android_log_write(ANDROID_LOG_ERROR, "libsigchain", buf); + va_end(ap); +} + +static void CheckSignalValid(int signal) { + if (signal <= 0 || signal >= _NSIG) { + log("Invalid signal %d", signal); + abort(); + } +} + +// Claim a signal chain for a particular signal. +void ClaimSignalChain(int signal, struct sigaction* oldaction) { + CheckSignalValid(signal); + user_sigactions[signal].Claim(*oldaction); +} + +void UnclaimSignalChain(int signal) { + CheckSignalValid(signal); + + user_sigactions[signal].Unclaim(signal); +} + +// Invoke the user's signal handler. +void InvokeUserSignalHandler(int sig, siginfo_t* info, void* context) { + // Check the arguments. + CheckSignalValid(sig); + + // The signal must have been claimed in order to get here. Check it. + if (!user_sigactions[sig].IsClaimed()) { + abort(); + } + + const struct sigaction& action = user_sigactions[sig].GetAction(); + + // Only deliver the signal if the signal was not masked out. + if (sigismember(&action.sa_mask, sig)) { + return; + } + if ((action.sa_flags & SA_SIGINFO) == 0) { + if (action.sa_handler != NULL) { + action.sa_handler(sig); + } + } else { + if (action.sa_sigaction != NULL) { + action.sa_sigaction(sig, info, context); + } + } +} + +extern "C" { +// These functions are C linkage since they replace the functions in libc. + +int sigaction(int signal, const struct sigaction* new_action, struct sigaction* old_action) { + // If this signal has been claimed as a signal chain, record the user's + // action but don't pass it on to the kernel. + // Note that we check that the signal number is in range here. An out of range signal + // number should behave exactly as the libc sigaction. + if (signal > 0 && signal < _NSIG && user_sigactions[signal].IsClaimed()) { + if (old_action != NULL) { + *old_action = user_sigactions[signal].GetAction(); + } + if (new_action != NULL) { + user_sigactions[signal].SetAction(*new_action); + } + return 0; + } + + // Will only get here if the signal chain has not been claimed. We want + // to pass the sigaction on to the kernel via the real sigaction in libc. + + void* linked_sigaction_sym = dlsym(RTLD_NEXT, "sigaction"); + if (linked_sigaction_sym == nullptr) { + log("Unable to find next sigaction in signal chain"); + abort(); + } + + typedef int (*SigAction)(int, const struct sigaction*, struct sigaction*); + SigAction linked_sigaction = reinterpret_cast(linked_sigaction_sym); + return linked_sigaction(signal, new_action, old_action); +} + + +int sigprocmask(int how, const sigset_t* bionic_new_set, sigset_t* bionic_old_set) { + const sigset_t* new_set_ptr = bionic_new_set; + sigset_t tmpset; + if (bionic_new_set != NULL) { + tmpset = *bionic_new_set; + + if (how == SIG_BLOCK) { + // Don't allow claimed signals in the mask. If a signal chain has been claimed + // we can't allow the user to block that signal. + for (int i = 0 ; i < _NSIG; ++i) { + if (user_sigactions[i].IsClaimed() && sigismember(&tmpset, i)) { + sigdelset(&tmpset, i); + } + } + } + new_set_ptr = &tmpset; + } + + void* linked_sigprocmask_sym = dlsym(RTLD_NEXT, "sigprocmask"); + if (linked_sigprocmask_sym == nullptr) { + log("Unable to find next sigprocmask in signal chain"); + abort(); + } + + typedef int (*SigProcMask)(int how, const sigset_t*, sigset_t*); + SigProcMask linked_sigprocmask= reinterpret_cast(linked_sigprocmask_sym); + return linked_sigprocmask(how, new_set_ptr, bionic_old_set); +} +} // extern "C" +} // namespace art + diff --git a/sigchainlib/sigchain.h b/sigchainlib/sigchain.h new file mode 100644 index 000000000..f6f2253d7 --- /dev/null +++ b/sigchainlib/sigchain.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#ifndef ART_SIGCHAINLIB_SIGCHAIN_H_ +#define ART_SIGCHAINLIB_SIGCHAIN_H_ + +#include +namespace art { + +void ClaimSignalChain(int signal, struct sigaction* oldaction); +void UnclaimSignalChain(int signal); +void InvokeUserSignalHandler(int sig, siginfo_t* info, void* context); + +} // namespace art + +#endif // ART_SIGCHAINLIB_SIGCHAIN_H_ diff --git a/test/Android.mk b/test/Android.mk index 8caa033b9..c15259c4b 100644 --- a/test/Android.mk +++ b/test/Android.mk @@ -46,6 +46,7 @@ TEST_OAT_DIRECTORIES := \ HelloWorld \ InterfaceTest \ JniTest \ + SignalTest \ NativeAllocations \ ParallelGC \ ReferenceMap \ diff --git a/test/SignalTest/SignalTest.java b/test/SignalTest/SignalTest.java new file mode 100644 index 000000000..7f15aeae7 --- /dev/null +++ b/test/SignalTest/SignalTest.java @@ -0,0 +1,60 @@ +/* + * 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. + */ + +class SignalTest { + private static native void initSignalTest(); + private static native void terminateSignalTest(); + private static native int testSignal(); + + private static void stackOverflow() { + stackOverflow(); + } + + public static void main(String[] args) { + System.loadLibrary("arttest"); + + System.out.println("init signal test"); + initSignalTest(); + try { + Object o = null; + int hash = o.hashCode(); + + // Should never get here. + System.out.println("hash: " + hash); + throw new AssertionError(); + } catch (NullPointerException e) { + System.out.println("Caught NullPointerException"); + } + try { + stackOverflow(); + + // Should never get here. + throw new AssertionError(); + } catch (StackOverflowError e) { + System.out.println("Caught StackOverflowError"); + } + + // Test that a signal in native code works. This will return + // the value 1234 if the signal is caught. + int x = testSignal(); + if (x != 1234) { + throw new AssertionError(); + } + + terminateSignalTest(); + System.out.println("Signal test OK"); + } +} diff --git a/test/SignalTest/signaltest.cc b/test/SignalTest/signaltest.cc new file mode 100644 index 000000000..b84e3957c --- /dev/null +++ b/test/SignalTest/signaltest.cc @@ -0,0 +1,74 @@ +/* + * 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 +#include +#include + +#include "jni.h" + +#ifdef __arm__ +#include +#endif + +static void signalhandler(int sig, siginfo_t* info, void* context) { + printf("signal caught\n"); +#ifdef __arm__ + // On ARM we do a more exhaustive test to make sure the signal + // context is OK. + // We can do this because we know that the instruction causing + // the signal is 2 bytes long (thumb mov instruction). On + // other architectures this is more difficult. + // TODO: we could do this on other architectures too if necessary, it's just harder. + struct ucontext *uc = reinterpret_cast(context); + struct sigcontext *sc = reinterpret_cast(&uc->uc_mcontext); + sc->arm_pc += 2; // Skip instruction causing segv. +#endif +} + +static struct sigaction oldaction; + +extern "C" JNIEXPORT void JNICALL Java_SignalTest_initSignalTest(JNIEnv*, jclass) { + struct sigaction action; + action.sa_sigaction = signalhandler; + sigemptyset(&action.sa_mask); + action.sa_flags = SA_SIGINFO | SA_ONSTACK; +#if !defined(__mips__) + action.sa_restorer = nullptr; +#endif + + sigaction(SIGSEGV, &action, &oldaction); +} + +extern "C" JNIEXPORT void JNICALL Java_SignalTest_terminateSignalTest(JNIEnv*, jclass) { + sigaction(SIGSEGV, &oldaction, nullptr); +} + +// Prevent the compiler being a smart-alec and optimizing out the assignment +// to nullptr. +char *p = nullptr; + +extern "C" JNIEXPORT jint JNICALL Java_SignalTest_testSignal(JNIEnv*, jclass) { +#ifdef __arm__ + // On ARM we cause a real SEGV. + *p = 'a'; +#else + // On other architectures we simulate SEGV. + kill(getpid(), SIGSEGV); +#endif + return 1234; +} + -- 2.11.0