crash_handler_unittest.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. // Copyright 2018 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. #include "components/gwp_asan/crash_handler/crash_handler.h"
  5. #include <map>
  6. #include <memory>
  7. #include <string>
  8. #include <vector>
  9. #include "base/callback_helpers.h"
  10. #include "base/command_line.h"
  11. #include "base/files/file_path.h"
  12. #include "base/files/scoped_temp_dir.h"
  13. #include "base/memory/page_size.h"
  14. #include "base/no_destructor.h"
  15. #include "base/path_service.h"
  16. #include "base/strings/stringprintf.h"
  17. #include "base/test/gtest_util.h"
  18. #include "base/test/multiprocess_test.h"
  19. #include "base/test/test_timeouts.h"
  20. #include "build/build_config.h"
  21. #include "components/gwp_asan/client/guarded_page_allocator.h"
  22. #include "components/gwp_asan/common/crash_key_name.h"
  23. #include "components/gwp_asan/crash_handler/crash.pb.h"
  24. #include "testing/gtest/include/gtest/gtest.h"
  25. #include "testing/multiprocess_func_list.h"
  26. #include "third_party/crashpad/crashpad/client/annotation.h"
  27. #include "third_party/crashpad/crashpad/client/crash_report_database.h"
  28. #include "third_party/crashpad/crashpad/client/crashpad_client.h"
  29. #include "third_party/crashpad/crashpad/client/crashpad_info.h"
  30. #include "third_party/crashpad/crashpad/handler/handler_main.h"
  31. #include "third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h"
  32. #include "third_party/crashpad/crashpad/tools/tool_support.h"
  33. #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
  34. #include "third_party/crashpad/crashpad/snapshot/sanitized/sanitization_information.h"
  35. #endif
  36. namespace gwp_asan {
  37. namespace internal {
  38. namespace {
  39. constexpr size_t kAllocationSize = 902;
  40. constexpr int kSuccess = 0;
  41. constexpr size_t kTotalPages = AllocatorState::kMaxSlots;
  42. #if !BUILDFLAG(IS_ANDROID)
  43. int HandlerMainAdaptor(int argc, char* argv[]) {
  44. crashpad::UserStreamDataSources user_stream_data_sources;
  45. user_stream_data_sources.push_back(
  46. std::make_unique<gwp_asan::UserStreamDataSource>());
  47. return crashpad::HandlerMain(argc, argv, &user_stream_data_sources);
  48. }
  49. // Child process that runs the crashpad handler.
  50. MULTIPROCESS_TEST_MAIN(CrashpadHandler) {
  51. base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  52. // Remove the --test-child-process argument from argv and launch crashpad.
  53. #if BUILDFLAG(IS_WIN)
  54. std::vector<wchar_t*> argv;
  55. for (auto& arg : cmd_line->argv()) {
  56. if (arg.find(L"test-child-process") == std::string::npos)
  57. argv.push_back(const_cast<wchar_t*>(arg.c_str()));
  58. }
  59. crashpad::ToolSupport::Wmain(argv.size(), argv.data(), HandlerMainAdaptor);
  60. #else
  61. std::vector<char*> argv;
  62. for (auto& arg : cmd_line->argv()) {
  63. if (arg.find("test-child-process") == std::string::npos)
  64. argv.push_back(const_cast<char*>(arg.c_str()));
  65. }
  66. HandlerMainAdaptor(argv.size(), argv.data());
  67. #endif
  68. return 0;
  69. }
  70. #endif // !BUILDFLAG(IS_ANDROID)
  71. // Child process that launches the crashpad handler and then crashes.
  72. MULTIPROCESS_TEST_MAIN(CrashingProcess) {
  73. #if BUILDFLAG(IS_APPLE)
  74. // Disable the system crash reporter from inspecting this crash (it is slow
  75. // and causes test timeouts.)
  76. crashpad::CrashpadInfo::GetCrashpadInfo()
  77. ->set_system_crash_reporter_forwarding(crashpad::TriState::kDisabled);
  78. #endif
  79. base::NoDestructor<GuardedPageAllocator> gpa;
  80. gpa->Init(AllocatorState::kMaxMetadata, AllocatorState::kMaxMetadata,
  81. kTotalPages, base::DoNothing(), false);
  82. base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
  83. base::FilePath directory = cmd_line->GetSwitchValuePath("directory");
  84. CHECK(!directory.empty());
  85. std::string test_name = cmd_line->GetSwitchValueASCII("test-name");
  86. CHECK(!test_name.empty());
  87. std::string allocator = cmd_line->GetSwitchValueASCII("allocator");
  88. const char* annotation_name;
  89. if (allocator == "malloc") {
  90. annotation_name = kMallocCrashKey;
  91. } else if (allocator == "partitionalloc") {
  92. annotation_name = kPartitionAllocCrashKey;
  93. } else {
  94. LOG(ERROR) << "Unknown allocator";
  95. return kSuccess;
  96. }
  97. std::string gpa_addr = gpa->GetCrashKey();
  98. static crashpad::Annotation gpa_annotation(
  99. crashpad::Annotation::Type::kString, annotation_name,
  100. const_cast<char*>(gpa_addr.c_str()));
  101. gpa_annotation.SetSize(gpa_addr.size());
  102. base::FilePath metrics_dir(FILE_PATH_LITERAL(""));
  103. std::map<std::string, std::string> annotations;
  104. std::vector<std::string> arguments;
  105. #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
  106. static crashpad::SanitizationInformation sanitization_info = {};
  107. static crashpad::SanitizationAllowedMemoryRanges allowed_memory_ranges;
  108. if (cmd_line->HasSwitch("sanitize")) {
  109. auto memory_ranges = gpa->GetInternalMemoryRegions();
  110. auto* range_array =
  111. new crashpad::SanitizationAllowedMemoryRanges::Range[memory_ranges
  112. .size()];
  113. for (size_t i = 0; i < memory_ranges.size(); i++) {
  114. range_array[i].base =
  115. reinterpret_cast<crashpad::VMAddress>(memory_ranges[i].first);
  116. range_array[i].length = memory_ranges[i].second;
  117. }
  118. allowed_memory_ranges.size = memory_ranges.size();
  119. allowed_memory_ranges.entries =
  120. reinterpret_cast<crashpad::VMAddress>(range_array);
  121. sanitization_info.allowed_memory_ranges_address =
  122. reinterpret_cast<crashpad::VMAddress>(&allowed_memory_ranges);
  123. arguments.push_back(base::StringPrintf("--sanitization-information=%p",
  124. &sanitization_info));
  125. }
  126. #endif
  127. #if !BUILDFLAG(IS_ANDROID)
  128. arguments.push_back("--test-child-process=CrashpadHandler");
  129. #endif
  130. crashpad::CrashpadClient* client = new crashpad::CrashpadClient();
  131. #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  132. bool handler =
  133. client->StartHandlerAtCrash(/* handler */ cmd_line->GetProgram(),
  134. /* database */ directory,
  135. /* metrics_dir */ metrics_dir,
  136. /* url */ "",
  137. /* annotations */ annotations,
  138. /* arguments */ arguments);
  139. #elif BUILDFLAG(IS_ANDROID)
  140. // TODO: Once the minSdkVersion is >= Q define a CrashpadHandlerMain() and
  141. // use the /system/bin/linker approach instead of using
  142. // libchrome_crashpad_handler.so
  143. base::FilePath modules;
  144. if (!base::PathService::Get(base::DIR_MODULE, &modules)) {
  145. LOG(ERROR) << "Failed to read DIR_MODULE";
  146. return kSuccess;
  147. }
  148. base::FilePath executable_path =
  149. modules.AppendASCII("libchrome_crashpad_handler.so");
  150. std::unique_ptr<base::Environment> env(base::Environment::Create());
  151. std::string library_path;
  152. env->GetVar("LD_LIBRARY_PATH", &library_path);
  153. env->SetVar("LD_LIBRARY_PATH", library_path + ":" + modules.value());
  154. bool handler = client->StartHandlerAtCrash(
  155. executable_path, directory, metrics_dir, "", annotations, arguments);
  156. #else
  157. bool handler = client->StartHandler(/* handler */ cmd_line->GetProgram(),
  158. /* database */ directory,
  159. /* metrics_dir */ metrics_dir,
  160. /* url */ "",
  161. /* annotations */ annotations,
  162. /* arguments */ arguments,
  163. /* restartable */ false,
  164. /* asynchronous_start */ false);
  165. #endif
  166. if (!handler) {
  167. LOG(ERROR) << "Crash handler failed to launch";
  168. return kSuccess;
  169. }
  170. if (test_name == "UseAfterFree") {
  171. void* ptr = gpa->Allocate(kAllocationSize);
  172. gpa->Deallocate(ptr);
  173. *(int*)ptr = 0;
  174. } else if (test_name == "DoubleFree") {
  175. void* ptr = gpa->Allocate(kAllocationSize);
  176. gpa->Deallocate(ptr);
  177. gpa->Deallocate(ptr);
  178. } else if (test_name == "Underflow") {
  179. void* ptr = gpa->Allocate(kAllocationSize);
  180. for (size_t i = 0; i < base::GetPageSize(); i++)
  181. ((unsigned char*)ptr)[-i] = 0;
  182. } else if (test_name == "Overflow") {
  183. void* ptr = gpa->Allocate(kAllocationSize);
  184. for (size_t i = 0; i <= base::GetPageSize(); i++)
  185. ((unsigned char*)ptr)[i] = 0;
  186. } else if (test_name == "UnrelatedException") {
  187. __builtin_trap();
  188. } else if (test_name == "FreeInvalidAddress") {
  189. void* ptr = gpa->Allocate(kAllocationSize);
  190. uintptr_t bad_address = reinterpret_cast<uintptr_t>(ptr) + 1;
  191. gpa->Deallocate(reinterpret_cast<void*>(bad_address));
  192. } else if (test_name == "MissingMetadata") {
  193. // Consume all allocations/metadata
  194. void* ptrs[AllocatorState::kMaxMetadata];
  195. for (size_t i = 0; i < AllocatorState::kMaxMetadata; i++)
  196. ptrs[i] = gpa->Allocate(1);
  197. gpa->Deallocate(ptrs[0]);
  198. // Take the remaining metadata slot with an allocation on a different page.
  199. while (true) {
  200. void* new_alloc = gpa->Allocate(1);
  201. if (new_alloc != ptrs[0])
  202. break;
  203. gpa->Deallocate(new_alloc);
  204. }
  205. // Cause a crash accessing an allocation that no longer has metadata
  206. // associated with it.
  207. *(uint8_t*)(ptrs[0]) = 0;
  208. } else {
  209. LOG(ERROR) << "Unknown test name " << test_name;
  210. }
  211. LOG(ERROR) << "This return should never be reached.";
  212. return kSuccess;
  213. }
  214. struct TestParams {
  215. TestParams(const char* allocator, bool sanitize)
  216. : allocator(allocator), sanitize(sanitize) {}
  217. const char* allocator;
  218. bool sanitize;
  219. };
  220. class CrashHandlerTest : public base::MultiProcessTest,
  221. public testing::WithParamInterface<TestParams> {
  222. protected:
  223. CrashHandlerTest() : params_(GetParam()) {}
  224. // Launch a child process and wait for it to crash. Set |gwp_asan_found_| if a
  225. // GWP-ASan data was found and if so, read it into |proto_|.
  226. void SetUp() final {
  227. base::ScopedTempDir database_dir;
  228. ASSERT_TRUE(database_dir.CreateUniqueTempDir());
  229. // Remove the parameterized test suffix from the test name.
  230. std::string test_name(
  231. testing::UnitTest::GetInstance()->current_test_info()->name());
  232. size_t separator = test_name.find("/");
  233. ASSERT_NE(separator, std::string::npos);
  234. test_name.erase(separator);
  235. ASSERT_TRUE(runTestProcess(database_dir.GetPath(), test_name.c_str()));
  236. bool minidump_found;
  237. readGwpAsanStreamFromCrash(database_dir.GetPath(), &minidump_found,
  238. &gwp_asan_found_, &proto_);
  239. ASSERT_TRUE(minidump_found);
  240. }
  241. // Launch a second process that installs a crashpad handler and causes an
  242. // exception of type test_name, then validate that it exited successfully.
  243. // crashpad is initialized to write to the given database directory.
  244. bool runTestProcess(const base::FilePath& database_dir,
  245. const char* test_name) {
  246. base::CommandLine cmd_line =
  247. base::GetMultiProcessTestChildBaseCommandLine();
  248. cmd_line.AppendSwitchPath("directory", database_dir);
  249. cmd_line.AppendSwitchASCII("test-name", test_name);
  250. cmd_line.AppendSwitchASCII("allocator", params_.allocator);
  251. if (params_.sanitize)
  252. cmd_line.AppendSwitch("sanitize");
  253. base::LaunchOptions options;
  254. #if BUILDFLAG(IS_WIN)
  255. options.start_hidden = true;
  256. #endif // BUILDFLAG(IS_WIN)
  257. base::Process process =
  258. base::SpawnMultiProcessTestChild("CrashingProcess", cmd_line, options);
  259. #if !BUILDFLAG(IS_ANDROID)
  260. int exit_code = -1;
  261. EXPECT_TRUE(WaitForMultiprocessTestChildExit(
  262. process, TestTimeouts::action_max_timeout(), &exit_code));
  263. EXPECT_NE(exit_code, kSuccess);
  264. return (exit_code != kSuccess);
  265. #else
  266. // TODO(https://crbug.com/976063): Android's implementation of
  267. // WaitForMultiprocessTestChildExit can't detect child process crashes, this
  268. // can be fixed after minSdkVersion >= Q.
  269. for (int i = 0; i < TestTimeouts::action_max_timeout().InSeconds(); i++) {
  270. if (kill(process.Pid(), 0) && errno == ESRCH)
  271. return true;
  272. sleep(1);
  273. }
  274. return false;
  275. #endif
  276. }
  277. // Given a directory with a single crashpad exception, read and parse the
  278. // minidump and identify whether it has a GWP-ASan stream. If it successfully
  279. // found a minidump, it writes true to minidump_found. If it sucessfully found
  280. // a GWP-ASan stream in the minidump, it writes true to gwp_asan_found and
  281. // parses the protobuf into into proto_out.
  282. void readGwpAsanStreamFromCrash(const base::FilePath& database_dir,
  283. bool* minidump_found,
  284. bool* gwp_asan_found,
  285. gwp_asan::Crash* proto_out) {
  286. *minidump_found = *gwp_asan_found = false;
  287. auto database =
  288. crashpad::CrashReportDatabase::InitializeWithoutCreating(database_dir);
  289. std::vector<crashpad::CrashReportDatabase::Report> reports;
  290. ASSERT_EQ(database->GetPendingReports(&reports),
  291. crashpad::CrashReportDatabase::kNoError);
  292. ASSERT_EQ(reports.size(), 1U);
  293. crashpad::FileReader minidump_file_reader;
  294. ASSERT_TRUE(minidump_file_reader.Open(reports[0].file_path));
  295. crashpad::ProcessSnapshotMinidump minidump_process_snapshot;
  296. ASSERT_TRUE(minidump_process_snapshot.Initialize(&minidump_file_reader));
  297. *minidump_found = true;
  298. auto custom_streams = minidump_process_snapshot.CustomMinidumpStreams();
  299. for (auto* stream : custom_streams) {
  300. if (stream->stream_type() == static_cast<crashpad::MinidumpStreamType>(
  301. kGwpAsanMinidumpStreamType)) {
  302. ASSERT_TRUE(proto_out->ParseFromArray(stream->data().data(),
  303. stream->data().size()));
  304. *gwp_asan_found = true;
  305. return;
  306. }
  307. }
  308. }
  309. void checkProto(Crash_ErrorType error_type, bool has_deallocation) {
  310. EXPECT_TRUE(proto_.has_error_type());
  311. EXPECT_EQ(proto_.error_type(), error_type);
  312. EXPECT_TRUE(proto_.has_allocation_address());
  313. EXPECT_TRUE(proto_.has_allocation_size());
  314. EXPECT_EQ(proto_.allocation_size(), kAllocationSize);
  315. EXPECT_TRUE(proto_.has_allocation());
  316. EXPECT_TRUE(proto_.allocation().has_thread_id());
  317. EXPECT_NE(proto_.allocation().thread_id(),
  318. static_cast<uint64_t>(base::kInvalidThreadId));
  319. EXPECT_GT(proto_.allocation().stack_trace_size(), 0);
  320. EXPECT_EQ(proto_.has_deallocation(), has_deallocation);
  321. if (has_deallocation) {
  322. EXPECT_TRUE(proto_.deallocation().has_thread_id());
  323. EXPECT_NE(proto_.deallocation().thread_id(),
  324. static_cast<uint64_t>(base::kInvalidThreadId));
  325. EXPECT_EQ(proto_.allocation().thread_id(),
  326. proto_.deallocation().thread_id());
  327. EXPECT_GT(proto_.deallocation().stack_trace_size(), 0);
  328. }
  329. EXPECT_TRUE(proto_.has_region_start());
  330. EXPECT_TRUE(proto_.has_region_size());
  331. EXPECT_EQ(proto_.region_start() & (base::GetPageSize() - 1), 0U);
  332. EXPECT_EQ(proto_.region_size(),
  333. base::GetPageSize() * (2 * kTotalPages + 1));
  334. EXPECT_TRUE(proto_.has_missing_metadata());
  335. EXPECT_FALSE(proto_.missing_metadata());
  336. EXPECT_TRUE(proto_.has_allocator());
  337. if (!strcmp(params_.allocator, "malloc"))
  338. EXPECT_EQ(proto_.allocator(), Crash_Allocator_MALLOC);
  339. else if (!strcmp(params_.allocator, "partitionalloc"))
  340. EXPECT_EQ(proto_.allocator(), Crash_Allocator_PARTITIONALLOC);
  341. else
  342. ASSERT_TRUE(false) << "Unknown allocator name";
  343. }
  344. gwp_asan::Crash proto_;
  345. TestParams params_;
  346. bool gwp_asan_found_;
  347. };
  348. #if defined(ADDRESS_SANITIZER) && (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_ANDROID))
  349. // ASan intercepts crashes and crashpad doesn't have a chance to see them.
  350. #define MAYBE_DISABLED(name) DISABLED_ ##name
  351. #else
  352. #define MAYBE_DISABLED(name) name
  353. #endif
  354. TEST_P(CrashHandlerTest, MAYBE_DISABLED(UseAfterFree)) {
  355. ASSERT_TRUE(gwp_asan_found_);
  356. checkProto(Crash_ErrorType_USE_AFTER_FREE, true);
  357. }
  358. TEST_P(CrashHandlerTest, MAYBE_DISABLED(DoubleFree)) {
  359. ASSERT_TRUE(gwp_asan_found_);
  360. checkProto(Crash_ErrorType_DOUBLE_FREE, true);
  361. }
  362. TEST_P(CrashHandlerTest, MAYBE_DISABLED(Underflow)) {
  363. ASSERT_TRUE(gwp_asan_found_);
  364. checkProto(Crash_ErrorType_BUFFER_UNDERFLOW, false);
  365. }
  366. TEST_P(CrashHandlerTest, MAYBE_DISABLED(Overflow)) {
  367. ASSERT_TRUE(gwp_asan_found_);
  368. checkProto(Crash_ErrorType_BUFFER_OVERFLOW, false);
  369. }
  370. TEST_P(CrashHandlerTest, MAYBE_DISABLED(FreeInvalidAddress)) {
  371. ASSERT_TRUE(gwp_asan_found_);
  372. checkProto(Crash_ErrorType_FREE_INVALID_ADDRESS, false);
  373. EXPECT_TRUE(proto_.has_free_invalid_address());
  374. }
  375. TEST_P(CrashHandlerTest, MAYBE_DISABLED(MissingMetadata)) {
  376. ASSERT_TRUE(gwp_asan_found_);
  377. ASSERT_TRUE(proto_.has_missing_metadata());
  378. EXPECT_TRUE(proto_.missing_metadata());
  379. EXPECT_FALSE(proto_.has_error_type());
  380. EXPECT_FALSE(proto_.has_allocation_address());
  381. EXPECT_FALSE(proto_.has_allocation_size());
  382. EXPECT_FALSE(proto_.has_allocation());
  383. EXPECT_FALSE(proto_.has_deallocation());
  384. EXPECT_FALSE(proto_.has_free_invalid_address());
  385. EXPECT_TRUE(proto_.has_region_start());
  386. EXPECT_TRUE(proto_.has_region_size());
  387. }
  388. TEST_P(CrashHandlerTest, MAYBE_DISABLED(UnrelatedException)) {
  389. ASSERT_FALSE(gwp_asan_found_);
  390. }
  391. INSTANTIATE_TEST_SUITE_P(VaryAllocator,
  392. CrashHandlerTest,
  393. testing::Values(
  394. #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
  395. TestParams("malloc", true),
  396. #endif
  397. TestParams("malloc", false),
  398. TestParams("partitionalloc", false)));
  399. } // namespace
  400. } // namespace internal
  401. } // namespace gwp_asan