Loading debuggerd/Android.bp +25 −3 Original line number Diff line number Diff line Loading @@ -169,6 +169,7 @@ cc_library_static { "libdebuggerd/backtrace.cpp", "libdebuggerd/gwp_asan.cpp", "libdebuggerd/open_files_list.cpp", "libdebuggerd/scudo.cpp", "libdebuggerd/tombstone.cpp", "libdebuggerd/utility.cpp", ], Loading @@ -176,8 +177,13 @@ cc_library_static { local_include_dirs: ["libdebuggerd/include"], export_include_dirs: ["libdebuggerd/include"], include_dirs: [ // Needed for private/bionic_fdsan.h include_dirs: ["bionic/libc"], "bionic/libc", // Needed for scudo/interface.h "external/scudo/standalone/include", ], header_libs: [ "bionic_libc_platform_headers", "gwp_asan_headers", Loading @@ -192,7 +198,10 @@ cc_library_static { "liblog", ], whole_static_libs: ["gwp_asan_crash_handler"], whole_static_libs: [ "gwp_asan_crash_handler", "libscudo", ], target: { recovery: { Loading @@ -206,6 +215,9 @@ cc_library_static { debuggable: { cflags: ["-DROOT_POSSIBLE"], }, experimental_mte: { cflags: ["-DANDROID_EXPERIMENTAL_MTE"], }, }, } Loading Loading @@ -256,6 +268,10 @@ cc_test { "gwp_asan_headers", ], include_dirs: [ "external/scudo/standalone/include", ], local_include_dirs: [ "libdebuggerd", ], Loading @@ -271,6 +287,12 @@ cc_test { }, test_suites: ["device-tests"], product_variables: { experimental_mte: { cflags: ["-DANDROID_EXPERIMENTAL_MTE"], }, }, } cc_benchmark { Loading debuggerd/crash_dump.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -289,6 +289,8 @@ static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo, process_info->fdsan_table_address = crash_info->data.d.fdsan_table_address; process_info->gwp_asan_state = crash_info->data.d.gwp_asan_state; process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata; process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot; process_info->scudo_region_info = crash_info->data.d.scudo_region_info; FALLTHROUGH_INTENDED; case 1: case 2: Loading debuggerd/debuggerd_test.cpp +170 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,9 @@ #include <android/fdsan.h> #include <android/set_abort_message.h> #include <bionic/malloc.h> #include <bionic/mte.h> #include <bionic/mte_kernel.h> #include <bionic/reserved_signals.h> #include <android-base/cmsg.h> Loading Loading @@ -331,6 +334,173 @@ TEST_F(CrasherTest, tagged_fault_addr) { R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr (0x100000000000dead|0xdead))"); } #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) static void SetTagCheckingLevelSync() { int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0); if (tagged_addr_ctrl < 0) { abort(); } tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_SYNC; if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) != 0) { abort(); } HeapTaggingLevel heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC; if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level))) { abort(); } } #endif TEST_F(CrasherTest, mte_uaf) { #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([]() { SetTagCheckingLevelSync(); volatile int* p = (volatile int*)malloc(16); free((void *)p); p[0] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a 16-byte allocation)"); #else GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE"; #endif } TEST_F(CrasherTest, mte_overflow) { #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([]() { SetTagCheckingLevelSync(); volatile int* p = (volatile int*)malloc(16); p[4] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)"); #else GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE"; #endif } TEST_F(CrasherTest, mte_underflow) { #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([]() { SetTagCheckingLevelSync(); volatile int* p = (volatile int*)malloc(16); p[-1] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a 16-byte allocation)"); #else GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE"; #endif } TEST_F(CrasherTest, mte_multiple_causes) { #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([]() { SetTagCheckingLevelSync(); // Make two allocations with the same tag and close to one another. Check for both properties // with a bounds check -- this relies on the fact that only if the allocations have the same tag // would they be measured as closer than 128 bytes to each other. Otherwise they would be about // (some non-zero value << 56) apart. // // The out-of-bounds access will be considered either an overflow of one or an underflow of the // other. std::set<uintptr_t> allocs; for (int i = 0; i != 4096; ++i) { uintptr_t alloc = reinterpret_cast<uintptr_t>(malloc(16)); auto it = allocs.insert(alloc).first; if (it != allocs.begin() && *std::prev(it) + 128 > alloc) { *reinterpret_cast<int*>(*std::prev(it) + 16) = 42; } if (std::next(it) != allocs.end() && alloc + 128 > *std::next(it)) { *reinterpret_cast<int*>(alloc + 16) = 42; } } }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_MATCH( result, R"(Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability.)"); // Adjacent untracked allocations may cause us to see the wrong underflow here (or only // overflows), so we can't match explicitly for an underflow message. ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)"); #else GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE"; #endif } TEST_F(CrasherTest, LD_PRELOAD) { int intercept_result; unique_fd output_fd; Loading debuggerd/include/debuggerd/handler.h +2 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ struct debugger_process_info { void* fdsan_table; const gwp_asan::AllocatorState* gwp_asan_state; const gwp_asan::AllocationMetadata* gwp_asan_metadata; const char* scudo_stack_depot; const char* scudo_region_info; }; // These callbacks are called in a signal handler, and thus must be async signal safe. Loading debuggerd/libdebuggerd/include/libdebuggerd/scudo.h 0 → 100644 +42 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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. */ #pragma once #include "types.h" #include "utility.h" #include <memory.h> #include "scudo/interface.h" class ScudoCrashData { public: ScudoCrashData() = delete; ~ScudoCrashData() = default; ScudoCrashData(unwindstack::Memory* process_memory, const ProcessInfo& process_info); bool CrashIsMine() const; void DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const; private: scudo_error_info error_info_ = {}; uintptr_t untagged_fault_addr_; void DumpReport(const scudo_error_report* report, log_t* log, unwindstack::Unwinder* unwinder) const; }; Loading
debuggerd/Android.bp +25 −3 Original line number Diff line number Diff line Loading @@ -169,6 +169,7 @@ cc_library_static { "libdebuggerd/backtrace.cpp", "libdebuggerd/gwp_asan.cpp", "libdebuggerd/open_files_list.cpp", "libdebuggerd/scudo.cpp", "libdebuggerd/tombstone.cpp", "libdebuggerd/utility.cpp", ], Loading @@ -176,8 +177,13 @@ cc_library_static { local_include_dirs: ["libdebuggerd/include"], export_include_dirs: ["libdebuggerd/include"], include_dirs: [ // Needed for private/bionic_fdsan.h include_dirs: ["bionic/libc"], "bionic/libc", // Needed for scudo/interface.h "external/scudo/standalone/include", ], header_libs: [ "bionic_libc_platform_headers", "gwp_asan_headers", Loading @@ -192,7 +198,10 @@ cc_library_static { "liblog", ], whole_static_libs: ["gwp_asan_crash_handler"], whole_static_libs: [ "gwp_asan_crash_handler", "libscudo", ], target: { recovery: { Loading @@ -206,6 +215,9 @@ cc_library_static { debuggable: { cflags: ["-DROOT_POSSIBLE"], }, experimental_mte: { cflags: ["-DANDROID_EXPERIMENTAL_MTE"], }, }, } Loading Loading @@ -256,6 +268,10 @@ cc_test { "gwp_asan_headers", ], include_dirs: [ "external/scudo/standalone/include", ], local_include_dirs: [ "libdebuggerd", ], Loading @@ -271,6 +287,12 @@ cc_test { }, test_suites: ["device-tests"], product_variables: { experimental_mte: { cflags: ["-DANDROID_EXPERIMENTAL_MTE"], }, }, } cc_benchmark { Loading
debuggerd/crash_dump.cpp +2 −0 Original line number Diff line number Diff line Loading @@ -289,6 +289,8 @@ static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo, process_info->fdsan_table_address = crash_info->data.d.fdsan_table_address; process_info->gwp_asan_state = crash_info->data.d.gwp_asan_state; process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata; process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot; process_info->scudo_region_info = crash_info->data.d.scudo_region_info; FALLTHROUGH_INTENDED; case 1: case 2: Loading
debuggerd/debuggerd_test.cpp +170 −0 Original line number Diff line number Diff line Loading @@ -31,6 +31,9 @@ #include <android/fdsan.h> #include <android/set_abort_message.h> #include <bionic/malloc.h> #include <bionic/mte.h> #include <bionic/mte_kernel.h> #include <bionic/reserved_signals.h> #include <android-base/cmsg.h> Loading Loading @@ -331,6 +334,173 @@ TEST_F(CrasherTest, tagged_fault_addr) { R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr (0x100000000000dead|0xdead))"); } #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) static void SetTagCheckingLevelSync() { int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0); if (tagged_addr_ctrl < 0) { abort(); } tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_SYNC; if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) != 0) { abort(); } HeapTaggingLevel heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC; if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level))) { abort(); } } #endif TEST_F(CrasherTest, mte_uaf) { #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([]() { SetTagCheckingLevelSync(); volatile int* p = (volatile int*)malloc(16); free((void *)p); p[0] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a 16-byte allocation)"); #else GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE"; #endif } TEST_F(CrasherTest, mte_overflow) { #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([]() { SetTagCheckingLevelSync(); volatile int* p = (volatile int*)malloc(16); p[4] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)"); #else GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE"; #endif } TEST_F(CrasherTest, mte_underflow) { #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([]() { SetTagCheckingLevelSync(); volatile int* p = (volatile int*)malloc(16); p[-1] = 42; }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))"); ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a 16-byte allocation)"); #else GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE"; #endif } TEST_F(CrasherTest, mte_multiple_causes) { #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; } int intercept_result; unique_fd output_fd; StartProcess([]() { SetTagCheckingLevelSync(); // Make two allocations with the same tag and close to one another. Check for both properties // with a bounds check -- this relies on the fact that only if the allocations have the same tag // would they be measured as closer than 128 bytes to each other. Otherwise they would be about // (some non-zero value << 56) apart. // // The out-of-bounds access will be considered either an overflow of one or an underflow of the // other. std::set<uintptr_t> allocs; for (int i = 0; i != 4096; ++i) { uintptr_t alloc = reinterpret_cast<uintptr_t>(malloc(16)); auto it = allocs.insert(alloc).first; if (it != allocs.begin() && *std::prev(it) + 128 > alloc) { *reinterpret_cast<int*>(*std::prev(it) + 16) = 42; } if (std::next(it) != allocs.end() && alloc + 128 > *std::next(it)) { *reinterpret_cast<int*>(alloc + 16) = 42; } } }); StartIntercept(&output_fd); FinishCrasher(); AssertDeath(SIGSEGV); FinishIntercept(&intercept_result); ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; std::string result; ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); ASSERT_MATCH( result, R"(Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability.)"); // Adjacent untracked allocations may cause us to see the wrong underflow here (or only // overflows), so we can't match explicitly for an underflow message. ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)"); #else GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE"; #endif } TEST_F(CrasherTest, LD_PRELOAD) { int intercept_result; unique_fd output_fd; Loading
debuggerd/include/debuggerd/handler.h +2 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ struct debugger_process_info { void* fdsan_table; const gwp_asan::AllocatorState* gwp_asan_state; const gwp_asan::AllocationMetadata* gwp_asan_metadata; const char* scudo_stack_depot; const char* scudo_region_info; }; // These callbacks are called in a signal handler, and thus must be async signal safe. Loading
debuggerd/libdebuggerd/include/libdebuggerd/scudo.h 0 → 100644 +42 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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. */ #pragma once #include "types.h" #include "utility.h" #include <memory.h> #include "scudo/interface.h" class ScudoCrashData { public: ScudoCrashData() = delete; ~ScudoCrashData() = default; ScudoCrashData(unwindstack::Memory* process_memory, const ProcessInfo& process_info); bool CrashIsMine() const; void DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const; private: scudo_error_info error_info_ = {}; uintptr_t untagged_fault_addr_; void DumpReport(const scudo_error_report* report, log_t* log, unwindstack::Unwinder* unwinder) const; };