From 7c2799f3ba6f8a8186c8883b213c3e59768b1287 Mon Sep 17 00:00:00 2001 From: Tobias Sargeant Date: Tue, 31 Jan 2017 13:42:52 +0000 Subject: Sanitize dumped stacks to remove data that may be identifiable. In order to sanitize the stack contents we erase any pointer-aligned word that could not be interpreted as a pointer into one of the processes' memory mappings, or a small integer (+/-4096). This still retains enough information to unwind stack frames, and also to recover some register values. BUG=682278 Change-Id: I541a13b2e92a9d1aea2c06a50bd769a9e25601d3 Reviewed-on: https://chromium-review.googlesource.com/430050 Reviewed-by: Robert Sesek --- .../linux_ptrace_dumper_unittest.cc | 181 +++++++++++++++------ 1 file changed, 130 insertions(+), 51 deletions(-) (limited to 'src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc') diff --git a/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc index be533e15..ae30e606 100644 --- a/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc +++ b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc @@ -66,6 +66,62 @@ using namespace google_breakpad; namespace { +pid_t SetupChildProcess(int number_of_threads) { + char kNumberOfThreadsArgument[2]; + sprintf(kNumberOfThreadsArgument, "%d", number_of_threads); + + int fds[2]; + EXPECT_NE(-1, pipe(fds)); + + pid_t child_pid = fork(); + if (child_pid == 0) { + // In child process. + close(fds[0]); + + string helper_path(GetHelperBinary()); + if (helper_path.empty()) { + ADD_FAILURE() << "Couldn't find helper binary"; + return -1; + } + + // Pass the pipe fd and the number of threads as arguments. + char pipe_fd_string[8]; + sprintf(pipe_fd_string, "%d", fds[1]); + execl(helper_path.c_str(), + "linux_dumper_unittest_helper", + pipe_fd_string, + kNumberOfThreadsArgument, + NULL); + // Kill if we get here. + printf("Errno from exec: %d", errno); + ADD_FAILURE() << "Exec of " << helper_path << " failed: " << strerror(errno); + return -1; + } + close(fds[1]); + + // Wait for all child threads to indicate that they have started + for (int threads = 0; threads < number_of_threads; threads++) { + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); + EXPECT_EQ(1, r); + EXPECT_TRUE(pfd.revents & POLLIN); + uint8_t junk; + EXPECT_EQ(read(fds[0], &junk, sizeof(junk)), + static_cast(sizeof(junk))); + } + close(fds[0]); + + // There is a race here because we may stop a child thread before + // it is actually running the busy loop. Empirically this sleep + // is sufficient to avoid the race. + usleep(100000); + return child_pid; +} + typedef wasteful_vector id_vector; typedef testing::Test LinuxPtraceDumperTest; @@ -370,58 +426,9 @@ TEST_F(LinuxPtraceDumperChildTest, FileIDsMatch) { TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) { static const int kNumberOfThreadsInHelperProgram = 5; - char kNumberOfThreadsArgument[2]; - sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram); - int fds[2]; - ASSERT_NE(-1, pipe(fds)); - - pid_t child_pid = fork(); - if (child_pid == 0) { - // In child process. - close(fds[0]); - - string helper_path(GetHelperBinary()); - if (helper_path.empty()) { - FAIL() << "Couldn't find helper binary"; - exit(1); - } - - // Pass the pipe fd and the number of threads as arguments. - char pipe_fd_string[8]; - sprintf(pipe_fd_string, "%d", fds[1]); - execl(helper_path.c_str(), - "linux_dumper_unittest_helper", - pipe_fd_string, - kNumberOfThreadsArgument, - NULL); - // Kill if we get here. - printf("Errno from exec: %d", errno); - FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno); - exit(0); - } - close(fds[1]); - - // Wait for all child threads to indicate that they have started - for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) { - struct pollfd pfd; - memset(&pfd, 0, sizeof(pfd)); - pfd.fd = fds[0]; - pfd.events = POLLIN | POLLERR; - - const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); - ASSERT_EQ(1, r); - ASSERT_TRUE(pfd.revents & POLLIN); - uint8_t junk; - ASSERT_EQ(read(fds[0], &junk, sizeof(junk)), - static_cast(sizeof(junk))); - } - close(fds[0]); - - // There is a race here because we may stop a child thread before - // it is actually running the busy loop. Empirically this sleep - // is sufficient to avoid the race. - usleep(100000); + pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram); + ASSERT_NE(child_pid, -1); // Children are ready now. LinuxPtraceDumper dumper(child_pid); @@ -468,3 +475,75 @@ TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) { ASSERT_TRUE(WIFSIGNALED(status)); ASSERT_EQ(SIGKILL, WTERMSIG(status)); } + +TEST_F(LinuxPtraceDumperTest, SanitizeStackCopy) { + static const int kNumberOfThreadsInHelperProgram = 1; + + pid_t child_pid = SetupChildProcess(kNumberOfThreadsInHelperProgram); + ASSERT_NE(child_pid, -1); + + LinuxPtraceDumper dumper(child_pid); + ASSERT_TRUE(dumper.Init()); + EXPECT_TRUE(dumper.ThreadsSuspend()); + + ThreadInfo thread_info; + EXPECT_TRUE(dumper.GetThreadInfoByIndex(0, &thread_info)); + + const void* stack; + size_t stack_len; + EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len, thread_info.stack_pointer)); + + uint8_t* stack_copy = new uint8_t[stack_len]; + dumper.CopyFromProcess(stack_copy, child_pid, stack, stack_len); + + size_t stack_offset = + thread_info.stack_pointer - reinterpret_cast(stack); + + const size_t word_count = (stack_len - stack_offset) / sizeof(uintptr_t); + uintptr_t* stack_words = new uintptr_t[word_count]; + + memcpy(stack_words, stack_copy + stack_offset, word_count * sizeof(uintptr_t)); + std::map pre_sanitization_words; + for (size_t i = 0; i < word_count; ++i) + ++pre_sanitization_words[stack_words[i]]; + + fprintf(stderr, "stack_offset=%lu stack_len=%lu stack=%p\n", stack_offset, stack_len, stack); + dumper.SanitizeStackCopy(stack_copy, stack_len, thread_info.stack_pointer, + stack_offset); + + // Memory below the stack pointer should be zeroed. + for (size_t i = 0; i < stack_offset; ++i) { + ASSERT_EQ(0, stack_copy[i]); + } + + memcpy(stack_words, stack_copy + stack_offset, word_count * sizeof(uintptr_t)); + std::map post_sanitization_words; + for (size_t i = 0; i < word_count; ++i) + ++post_sanitization_words[stack_words[i]]; + + std::set words; + for (auto &word : pre_sanitization_words) words.insert(word.first); + for (auto &word : post_sanitization_words) words.insert(word.first); + + for (auto word : words) { + if (word == static_cast(0X0DEFACED0DEFACEDull)) { + continue; + } + + bool should_be_sanitized = true; + if (static_cast(word) <= 4096 && + static_cast(word) >= -4096) should_be_sanitized = false; + if (dumper.FindMappingNoBias(word)) should_be_sanitized = false; + + ASSERT_EQ(should_be_sanitized, post_sanitization_words[word] == 0); + } + + EXPECT_TRUE(dumper.ThreadsResume()); + kill(child_pid, SIGKILL); + + // Reap child + int status; + ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0))); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(SIGKILL, WTERMSIG(status)); +} -- cgit v1.2.1