Merge "[MTE] Print cause and alloc/dealloc traces to logcat."

This commit is contained in:
Mitch Phillips 2021-06-03 18:21:58 +00:00 committed by Gerrit Code Review
commit ca4a4d2690
2 changed files with 73 additions and 42 deletions

View file

@ -51,6 +51,7 @@
#include <android-base/test_utils.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <libminijail.h>
@ -65,6 +66,7 @@ using namespace std::chrono_literals;
using android::base::SendFileDescriptors;
using android::base::unique_fd;
using ::testing::HasSubstr;
#if defined(__LP64__)
#define ARCH_SUFFIX "64"
@ -307,6 +309,19 @@ static void ConsumeFd(unique_fd fd, std::string* output) {
*output = std::move(result);
}
class LogcatCollector {
public:
LogcatCollector() { system("logcat -c"); }
void Collect(std::string* output) {
FILE* cmd_stdout = popen("logcat -d '*:S DEBUG'", "r");
ASSERT_NE(cmd_stdout, nullptr);
unique_fd tmp_fd(TEMP_FAILURE_RETRY(dup(fileno(cmd_stdout))));
ConsumeFd(std::move(tmp_fd), output);
pclose(cmd_stdout);
}
};
TEST_F(CrasherTest, smoke) {
int intercept_result;
unique_fd output_fd;
@ -441,6 +456,7 @@ TEST_P(GwpAsanCrasherTest, gwp_asan_uaf) {
}
GwpAsanTestParameters params = GetParam();
LogcatCollector logcat_collector;
int intercept_result;
unique_fd output_fd;
@ -460,17 +476,18 @@ TEST_P(GwpAsanCrasherTest, gwp_asan_uaf) {
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
std::string result;
ConsumeFd(std::move(output_fd), &result);
std::vector<std::string> log_sources(2);
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
if (params.free_before_access) {
ASSERT_MATCH(result, R"(deallocated by thread .*
#00 pc)");
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
if (params.free_before_access) {
ASSERT_MATCH(result, R"(deallocated by thread .*\n.*#00 pc)");
}
ASSERT_MATCH(result, R"((^|\s)allocated by thread .*\n.*#00 pc)");
}
ASSERT_MATCH(result, R"(allocated by thread .*
#00 pc)");
}
struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};
@ -488,6 +505,8 @@ TEST_P(SizeParamCrasherTest, mte_uaf) {
return;
}
LogcatCollector logcat_collector;
int intercept_result;
unique_fd output_fd;
StartProcess([&]() {
@ -504,16 +523,17 @@ TEST_P(SizeParamCrasherTest, mte_uaf) {
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
std::string result;
ConsumeFd(std::move(output_fd), &result);
std::vector<std::string> log_sources(2);
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"(deallocated by thread .*
#00 pc)");
ASSERT_MATCH(result, R"(allocated by thread .*
#00 pc)");
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"(deallocated by thread .*?\n.*#00 pc)");
ASSERT_MATCH(result, R"((^|\s)allocated by thread .*?\n.*#00 pc)");
}
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@ -557,6 +577,7 @@ TEST_P(SizeParamCrasherTest, mte_overflow) {
GTEST_SKIP() << "Requires MTE";
}
LogcatCollector logcat_collector;
int intercept_result;
unique_fd output_fd;
StartProcess([&]() {
@ -572,14 +593,16 @@ TEST_P(SizeParamCrasherTest, mte_overflow) {
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
std::string result;
ConsumeFd(std::move(output_fd), &result);
std::vector<std::string> log_sources(2);
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"(allocated by thread .*
#00 pc)");
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"((^|\s)allocated by thread .*?\n.*#00 pc)");
}
#else
GTEST_SKIP() << "Requires aarch64";
#endif
@ -612,7 +635,7 @@ TEST_P(SizeParamCrasherTest, mte_underflow) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
std::to_string(GetParam()) + R"(-byte allocation)");
ASSERT_MATCH(result, R"(allocated by thread .*
ASSERT_MATCH(result, R"((^|\s)allocated by thread .*
#00 pc)");
#else
GTEST_SKIP() << "Requires aarch64";
@ -625,6 +648,8 @@ TEST_F(CrasherTest, mte_multiple_causes) {
GTEST_SKIP() << "Requires MTE";
}
LogcatCollector logcat_collector;
int intercept_result;
unique_fd output_fd;
StartProcess([]() {
@ -657,17 +682,23 @@ TEST_F(CrasherTest, mte_multiple_causes) {
ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
std::string result;
ConsumeFd(std::move(output_fd), &result);
std::vector<std::string> log_sources(2);
ConsumeFd(std::move(output_fd), &log_sources[0]);
logcat_collector.Collect(&log_sources[1]);
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)");
for (const auto& result : log_sources) {
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_THAT(result, HasSubstr("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)");
// Ensure there's at least two allocation traces (one for each cause).
ASSERT_MATCH(
result,
R"((^|\s)allocated by thread .*?\n.*#00 pc(.|\n)*?(^|\s)allocated by thread .*?\n.*#00 pc)");
}
#else
GTEST_SKIP() << "Requires aarch64";
#endif

View file

@ -272,14 +272,14 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
if (tombstone.causes_size() > 1) {
CBS("");
CBS("Note: multiple potential causes for this crash were detected, listing them in decreasing "
CBL("Note: multiple potential causes for this crash were detected, listing them in decreasing "
"order of probability.");
}
for (const Cause& cause : tombstone.causes()) {
if (tombstone.causes_size() > 1) {
CBS("");
CBS("Cause: %s", cause.human_readable().c_str());
CBL("Cause: %s", cause.human_readable().c_str());
}
if (cause.has_memory_error() && cause.memory_error().has_heap()) {
@ -287,14 +287,14 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
if (heap_object.deallocation_backtrace_size() != 0) {
CBS("");
CBS("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), false);
CBL("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), true);
}
if (heap_object.allocation_backtrace_size() != 0) {
CBS("");
CBS("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), false);
CBL("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), true);
}
}
}