1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/gwp_asan/crash_handler/crash_analyzer.h"
6
7 #include <memory>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
12 #include "base/callback_helpers.h"
13 #include "base/debug/stack_trace.h"
14 #include "base/test/gtest_util.h"
15 #include "base/test/metrics/histogram_tester.h"
16 #include "build/build_config.h"
17 #include "components/gwp_asan/client/guarded_page_allocator.h"
18 #include "components/gwp_asan/common/allocator_state.h"
19 #include "components/gwp_asan/common/crash_key_name.h"
20 #include "components/gwp_asan/crash_handler/crash.pb.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "third_party/crashpad/crashpad/client/annotation.h"
24 #include "third_party/crashpad/crashpad/snapshot/annotation_snapshot.h"
25 #include "third_party/crashpad/crashpad/snapshot/cpu_context.h"
26 #include "third_party/crashpad/crashpad/snapshot/test/test_exception_snapshot.h"
27 #include "third_party/crashpad/crashpad/snapshot/test/test_module_snapshot.h"
28 #include "third_party/crashpad/crashpad/snapshot/test/test_process_snapshot.h"
29 #include "third_party/crashpad/crashpad/test/process_type.h"
30 #include "third_party/crashpad/crashpad/util/process/process_memory_native.h"
31
32 namespace gwp_asan {
33 namespace internal {
34
35 namespace {
36
37 constexpr const char* kMallocHistogramName =
38 "GwpAsan.CrashAnalysisResult.Malloc";
39 constexpr const char* kPartitionAllocHistogramName =
40 "GwpAsan.CrashAnalysisResult.PartitionAlloc";
41
42 } // namespace
43
44 class CrashAnalyzerTest : public testing::Test {
45 protected:
SetUp()46 void SetUp() final {
47 gpa_.Init(1, 1, 1, base::DoNothing(), false);
48 InitializeSnapshot();
49 }
50
51 // Initializes the ProcessSnapshot so that it appears the given allocator was
52 // used for backing malloc.
InitializeSnapshot()53 void InitializeSnapshot() {
54 std::string crash_key_value = gpa_.GetCrashKey();
55 std::vector<uint8_t> crash_key_vector(crash_key_value.begin(),
56 crash_key_value.end());
57
58 std::vector<crashpad::AnnotationSnapshot> annotations;
59 annotations.emplace_back(
60 kMallocCrashKey,
61 static_cast<uint16_t>(crashpad::Annotation::Type::kString),
62 crash_key_vector);
63
64 auto module = std::make_unique<crashpad::test::TestModuleSnapshot>();
65 module->SetAnnotationObjects(annotations);
66
67 auto exception = std::make_unique<crashpad::test::TestExceptionSnapshot>();
68 // Just match the bitness, the actual architecture doesn't matter.
69 #if defined(ARCH_CPU_64_BITS)
70 exception->MutableContext()->architecture =
71 crashpad::CPUArchitecture::kCPUArchitectureX86_64;
72 #else
73 exception->MutableContext()->architecture =
74 crashpad::CPUArchitecture::kCPUArchitectureX86;
75 #endif
76
77 auto memory = std::make_unique<crashpad::ProcessMemoryNative>();
78 ASSERT_TRUE(memory->Initialize(crashpad::test::GetSelfProcess()));
79
80 process_snapshot_.AddModule(std::move(module));
81 process_snapshot_.SetException(std::move(exception));
82 process_snapshot_.SetProcessMemory(std::move(memory));
83 }
84
85 GuardedPageAllocator gpa_;
86 crashpad::test::TestProcessSnapshot process_snapshot_;
87 };
88
89 // Stack trace collection on Android builds with frame pointers enabled does
90 // not use base::debug::StackTrace, so the stack traces may vary slightly and
91 // break this test.
92 #if !defined(OS_ANDROID) || !BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
TEST_F(CrashAnalyzerTest,StackTraceCollection)93 TEST_F(CrashAnalyzerTest, StackTraceCollection) {
94 void* ptr = gpa_.Allocate(10);
95 ASSERT_NE(ptr, nullptr);
96 gpa_.Deallocate(ptr);
97
98 // Lets pretend a double free() occurred on the allocation we saw previously.
99 gpa_.state_.double_free_address = reinterpret_cast<uintptr_t>(ptr);
100
101 base::HistogramTester histogram_tester;
102 gwp_asan::Crash proto;
103 bool proto_present =
104 CrashAnalyzer::GetExceptionInfo(process_snapshot_, &proto);
105 ASSERT_TRUE(proto_present);
106
107 int result = static_cast<int>(GwpAsanCrashAnalysisResult::kGwpAsanCrash);
108 EXPECT_THAT(histogram_tester.GetAllSamples(kMallocHistogramName),
109 testing::ElementsAre(base::Bucket(result, 1)));
110 histogram_tester.ExpectTotalCount(kPartitionAllocHistogramName, 0);
111
112 ASSERT_TRUE(proto.has_allocation());
113 ASSERT_TRUE(proto.has_deallocation());
114
115 base::debug::StackTrace st;
116 size_t trace_len;
117 const void* const* trace = st.Addresses(&trace_len);
118 ASSERT_NE(trace, nullptr);
119 ASSERT_GT(trace_len, 0U);
120
121 // Adjust the stack trace to point to the entry above the current frame.
122 while (trace_len > 0) {
123 if (trace[0] == __builtin_return_address(0))
124 break;
125
126 trace++;
127 trace_len--;
128 }
129
130 ASSERT_GT(proto.allocation().stack_trace_size(), (int)trace_len);
131 ASSERT_GT(proto.deallocation().stack_trace_size(), (int)trace_len);
132
133 // Ensure that the allocation and deallocation stack traces match the stack
134 // frames that we collected above the current frame.
135 for (size_t i = 1; i <= trace_len; i++) {
136 SCOPED_TRACE(i);
137 ASSERT_EQ(proto.allocation().stack_trace(
138 proto.allocation().stack_trace_size() - i),
139 reinterpret_cast<uintptr_t>(trace[trace_len - i]));
140 ASSERT_EQ(proto.deallocation().stack_trace(
141 proto.deallocation().stack_trace_size() - i),
142 reinterpret_cast<uintptr_t>(trace[trace_len - i]));
143 }
144 }
145 #endif
146
TEST_F(CrashAnalyzerTest,InternalError)147 TEST_F(CrashAnalyzerTest, InternalError) {
148 // Lets pretend an invalid free() occurred in the allocator region.
149 gpa_.state_.free_invalid_address =
150 reinterpret_cast<uintptr_t>(gpa_.state_.first_page_addr);
151 // Out of bounds slot_to_metadata_idx, allocator was initialized with only a
152 // single entry slot/metadata entry.
153 gpa_.slot_to_metadata_idx_[0] = 5;
154
155 base::HistogramTester histogram_tester;
156 gwp_asan::Crash proto;
157 bool proto_present =
158 CrashAnalyzer::GetExceptionInfo(process_snapshot_, &proto);
159 ASSERT_TRUE(proto_present);
160
161 int result =
162 static_cast<int>(GwpAsanCrashAnalysisResult::kErrorBadMetadataIndex);
163 EXPECT_THAT(histogram_tester.GetAllSamples(kMallocHistogramName),
164 testing::ElementsAre(base::Bucket(result, 1)));
165 histogram_tester.ExpectTotalCount(kPartitionAllocHistogramName, 0);
166
167 EXPECT_TRUE(proto.has_internal_error());
168 ASSERT_TRUE(proto.has_missing_metadata());
169 EXPECT_TRUE(proto.missing_metadata());
170 }
171
172 } // namespace internal
173 } // namespace gwp_asan
174