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/bind_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