Import Cobalt 21.master.0.260628
diff --git a/src/third_party/crashpad/util/win/address_types.h b/src/third_party/crashpad/util/win/address_types.h new file mode 100644 index 0000000..f26a57e --- /dev/null +++ b/src/third_party/crashpad/util/win/address_types.h
@@ -0,0 +1,32 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_ADDRESS_TYPES_H_ +#define CRASHPAD_UTIL_WIN_ADDRESS_TYPES_H_ + +#include <stdint.h> + +namespace crashpad { + +//! \brief Type used to represent an address in a process, potentially across +//! bitness. +using WinVMAddress = uint64_t; + +//! \brief Type used to represent the size of a memory range (with a +//! WinVMAddress), potentially across bitness. +using WinVMSize = uint64_t; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_ADDRESS_TYPES_H_
diff --git a/src/third_party/crashpad/util/win/checked_win_address_range.h b/src/third_party/crashpad/util/win/checked_win_address_range.h new file mode 100644 index 0000000..e86c7b7 --- /dev/null +++ b/src/third_party/crashpad/util/win/checked_win_address_range.h
@@ -0,0 +1,36 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_CHECKED_WIN_ADDRESS_RANGE_H_ +#define CRASHPAD_UTIL_WIN_CHECKED_WIN_ADDRESS_RANGE_H_ + +#include "util/numeric/checked_address_range.h" +#include "util/win/address_types.h" + +namespace crashpad { + +//! \brief Ensures that a range, composed of a base and a size, does not +//! overflow the pointer type of the process it describes a range in. +//! +//! This class checks bases of type WinVMAddress and sizes of type WinVMSize +//! against a process whose pointer type is either 32 or 64 bits wide. +//! +//! Aside from varying the overall range on the basis of a process' pointer type +//! width, this class functions very similarly to CheckedRange. +using CheckedWinAddressRange = + internal::CheckedAddressRangeGeneric<WinVMAddress, WinVMSize>; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_CHECKED_WIN_ADDRESS_RANGE_H_
diff --git a/src/third_party/crashpad/util/win/command_line.cc b/src/third_party/crashpad/util/win/command_line.cc new file mode 100644 index 0000000..c015eed --- /dev/null +++ b/src/third_party/crashpad/util/win/command_line.cc
@@ -0,0 +1,60 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/command_line.h" + +#include <sys/types.h> + +namespace crashpad { + +// Ref: +// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ +void AppendCommandLineArgument(const std::wstring& argument, + std::wstring* command_line) { + if (!command_line->empty()) { + command_line->push_back(L' '); + } + + // Don’t bother quoting if unnecessary. + if (!argument.empty() && + argument.find_first_of(L" \t\n\v\"") == std::wstring::npos) { + command_line->append(argument); + } else { + command_line->push_back(L'"'); + for (std::wstring::const_iterator i = argument.begin();; ++i) { + size_t backslash_count = 0; + while (i != argument.end() && *i == L'\\') { + ++i; + ++backslash_count; + } + if (i == argument.end()) { + // Escape all backslashes, but let the terminating double quotation mark + // we add below be interpreted as a metacharacter. + command_line->append(backslash_count * 2, L'\\'); + break; + } else if (*i == L'"') { + // Escape all backslashes and the following double quotation mark. + command_line->append(backslash_count * 2 + 1, L'\\'); + command_line->push_back(*i); + } else { + // Backslashes aren’t special here. + command_line->append(backslash_count, L'\\'); + command_line->push_back(*i); + } + } + command_line->push_back(L'"'); + } +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/command_line.h b/src/third_party/crashpad/util/win/command_line.h new file mode 100644 index 0000000..c12d653 --- /dev/null +++ b/src/third_party/crashpad/util/win/command_line.h
@@ -0,0 +1,38 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_COMMAND_LINE_H_ +#define CRASHPAD_UTIL_WIN_COMMAND_LINE_H_ + +#include <string> + +namespace crashpad { + +//! \brief Utility function for building escaped command lines. +//! +//! This builds a command line so that individual arguments can be reliably +//! decoded by `CommandLineToArgvW()`. +//! +//! \a argument is appended to \a command_line. If necessary, it will be placed +//! in quotation marks and escaped properly. If \a command_line is initially +//! non-empty, a space will precede \a argument. +//! +//! \param[in] argument The argument to append to \a command_line. +//! \param[in,out] command_line The command line being constructed. +void AppendCommandLineArgument(const std::wstring& argument, + std::wstring* command_line); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_COMMAND_LINE_H_
diff --git a/src/third_party/crashpad/util/win/command_line_test.cc b/src/third_party/crashpad/util/win/command_line_test.cc new file mode 100644 index 0000000..032d555 --- /dev/null +++ b/src/third_party/crashpad/util/win/command_line_test.cc
@@ -0,0 +1,168 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/command_line.h" + +#include <windows.h> +#include <shellapi.h> +#include <sys/types.h> + +#include "base/logging.h" +#include "base/scoped_generic.h" +#include "base/stl_util.h" +#include "gtest/gtest.h" +#include "test/errors.h" +#include "util/win/scoped_local_alloc.h" + +namespace crashpad { +namespace test { +namespace { + +// Calls AppendCommandLineArgument() for every argument in argv, then calls +// CommandLineToArgvW() to decode the string into a vector again, and compares +// the input and output. +void AppendCommandLineArgumentTest(size_t argc, const wchar_t* const argv[]) { + std::wstring command_line; + for (size_t index = 0; index < argc; ++index) { + AppendCommandLineArgument(argv[index], &command_line); + } + + int test_argc; + wchar_t** test_argv = CommandLineToArgvW(command_line.c_str(), &test_argc); + + ASSERT_TRUE(test_argv) << ErrorMessage("CommandLineToArgvW"); + ScopedLocalAlloc test_argv_owner(test_argv); + ASSERT_EQ(test_argc, static_cast<int>(argc)); + + for (size_t index = 0; index < argc; ++index) { + EXPECT_STREQ(argv[index], test_argv[index]) << "index " << index; + } + EXPECT_FALSE(test_argv[argc]); +} + +TEST(CommandLine, AppendCommandLineArgument) { + // Most of these test cases come from + // https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/, + // which was also a reference for the implementation of + // AppendCommandLineArgument(). + + { + SCOPED_TRACE("simple"); + + static constexpr const wchar_t* kArguments[] = { + L"child.exe", + L"argument 1", + L"argument 2", + }; + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); + } + + { + SCOPED_TRACE("path with spaces"); + + static constexpr const wchar_t* kArguments[] = { + L"child.exe", + L"argument1", + L"argument 2", + L"\\some\\path with\\spaces", + }; + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); + } + + { + SCOPED_TRACE("argument with embedded quotation marks"); + + static constexpr const wchar_t* kArguments[] = { + L"child.exe", + L"argument1", + L"she said, \"you had me at hello\"", + L"\\some\\path with\\spaces", + }; + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); + } + + { + SCOPED_TRACE("argument with unbalanced quotation marks"); + + static constexpr const wchar_t* kArguments[] = { + L"child.exe", + L"argument1", + L"argument\"2", + L"argument3", + L"argument4", + }; + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); + } + + { + SCOPED_TRACE("argument ending with backslash"); + + static constexpr const wchar_t* kArguments[] = { + L"child.exe", + L"\\some\\directory with\\spaces\\", + L"argument2", + }; + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); + } + + { + SCOPED_TRACE("empty argument"); + + static constexpr const wchar_t* kArguments[] = { + L"child.exe", + L"", + L"argument2", + }; + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); + } + + { + SCOPED_TRACE("funny nonprintable characters"); + + static constexpr const wchar_t* kArguments[] = { + L"child.exe", + L"argument 1", + L"argument\t2", + L"argument\n3", + L"argument\v4", + L"argument\"5", + L" ", + L"\t", + L"\n", + L"\v", + L"\"", + L" x", + L"\tx", + L"\nx", + L"\vx", + L"\"x", + L"x ", + L"x\t", + L"x\n", + L"x\v", + L"x\"", + L" ", + L"\t\t", + L"\n\n", + L"\v\v", + L"\"\"", + L" \t\n\v\"", + }; + AppendCommandLineArgumentTest(base::size(kArguments), kArguments); + } +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/context_wrappers.h b/src/third_party/crashpad/util/win/context_wrappers.h new file mode 100644 index 0000000..5d9b183 --- /dev/null +++ b/src/third_party/crashpad/util/win/context_wrappers.h
@@ -0,0 +1,40 @@ +// Copyright 2018 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_CONTEXT_WRAPPERS_H_ +#define CRASHPAD_UTIL_WIN_CONTEXT_WRAPPERS_H_ + +#include <windows.h> + +#include "build/build_config.h" + +namespace crashpad { + +//! \brief Retrieve program counter from `CONTEXT` structure for different +//! architectures supported by Windows. +inline void* ProgramCounterFromCONTEXT(const CONTEXT* context) { +#if defined(ARCH_CPU_X86) + return reinterpret_cast<void*>(context->Eip); +#elif defined(ARCH_CPU_X86_64) + return reinterpret_cast<void*>(context->Rip); +#elif defined(ARCH_CPU_ARM64) + return reinterpret_cast<void*>(context->Pc); +#else +#error Unsupported Windows Arch +#endif // ARCH_CPU_X86 +} + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_CONTEXT_WRAPPERS_H_
diff --git a/src/third_party/crashpad/util/win/critical_section_with_debug_info.cc b/src/third_party/crashpad/util/win/critical_section_with_debug_info.cc new file mode 100644 index 0000000..1613a51 --- /dev/null +++ b/src/third_party/crashpad/util/win/critical_section_with_debug_info.cc
@@ -0,0 +1,72 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/critical_section_with_debug_info.h" + +#include "base/logging.h" +#include "util/win/get_function.h" + +namespace crashpad { + +namespace { + +bool CrashpadInitializeCriticalSectionEx( + CRITICAL_SECTION* critical_section, + DWORD spin_count, + DWORD flags) { + static const auto initialize_critical_section_ex = + GET_FUNCTION_REQUIRED(L"kernel32.dll", ::InitializeCriticalSectionEx); + BOOL ret = + initialize_critical_section_ex(critical_section, spin_count, flags); + if (!ret) { + PLOG(ERROR) << "InitializeCriticalSectionEx"; + return false; + } + return true; +} + +} // namespace + +bool InitializeCriticalSectionWithDebugInfoIfPossible( + CRITICAL_SECTION* critical_section) { + // On XP and Vista, a plain initialization causes the CRITICAL_SECTION to be + // allocated with .DebugInfo. On 8 and above, we can pass an additional flag + // to InitializeCriticalSectionEx() to force the .DebugInfo on. Before Win 8, + // that flag causes InitializeCriticalSectionEx() to fail. So, for XP, Vista, + // and 7 we use InitializeCriticalSection(), and for 8 and above, + // InitializeCriticalSectionEx() with the additional flag. + // + // TODO(scottmg): Try to find a solution for Win 7. It's unclear how to force + // it on for Win 7, however the Loader Lock does have .DebugInfo so there may + // be a way to do it. The comments in winnt.h imply that perhaps it's passed + // to InitializeCriticalSectionAndSpinCount() as the top bits of the spin + // count, but that doesn't appear to work. For now, we initialize a valid + // CRITICAL_SECTION, but without .DebugInfo. + + const DWORD version = GetVersion(); + const DWORD major_version = LOBYTE(LOWORD(version)); + const DWORD minor_version = HIBYTE(LOWORD(version)); + const bool win7_or_lower = + major_version < 6 || (major_version == 6 && minor_version <= 1); + + if (win7_or_lower) { + InitializeCriticalSection(critical_section); + return true; + } + + return CrashpadInitializeCriticalSectionEx( + critical_section, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO); +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/critical_section_with_debug_info.h b/src/third_party/crashpad/util/win/critical_section_with_debug_info.h new file mode 100644 index 0000000..1271685 --- /dev/null +++ b/src/third_party/crashpad/util/win/critical_section_with_debug_info.h
@@ -0,0 +1,34 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_CRITICAL_SECTION_WITH_DEBUG_INFO_H_ +#define CRASHPAD_UTIL_WIN_CRITICAL_SECTION_WITH_DEBUG_INFO_H_ + +#include <windows.h> + +namespace crashpad { + +//! \brief Equivalent to `InitializeCritialSection()`, but attempts to allocate +//! with a valid `.DebugInfo` field on versions of Windows where it's +//! possible to do so. +//! +//! \return `true` on success, or `false` on failure with a message logged. +//! Success means that the critical section was successfully initialized, +//! but it does not necessarily have a valid `.DebugInfo` field. +bool InitializeCriticalSectionWithDebugInfoIfPossible( + CRITICAL_SECTION* critical_section); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_CRITICAL_SECTION_WITH_DEBUG_INFO_H_
diff --git a/src/third_party/crashpad/util/win/critical_section_with_debug_info_test.cc b/src/third_party/crashpad/util/win/critical_section_with_debug_info_test.cc new file mode 100644 index 0000000..dcc5965 --- /dev/null +++ b/src/third_party/crashpad/util/win/critical_section_with_debug_info_test.cc
@@ -0,0 +1,34 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/critical_section_with_debug_info.h" + +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(CriticalSectionWithDebugInfo, CriticalSectionWithDebugInfo) { + CRITICAL_SECTION critical_section; + ASSERT_TRUE( + InitializeCriticalSectionWithDebugInfoIfPossible(&critical_section)); + EnterCriticalSection(&critical_section); + LeaveCriticalSection(&critical_section); + DeleteCriticalSection(&critical_section); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/exception_handler_server.cc b/src/third_party/crashpad/util/win/exception_handler_server.cc new file mode 100644 index 0000000..2593ff2 --- /dev/null +++ b/src/third_party/crashpad/util/win/exception_handler_server.cc
@@ -0,0 +1,580 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/exception_handler_server.h" + +#include <stdint.h> +#include <string.h> +#include <sys/types.h> + +#include <utility> + +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "minidump/minidump_file_writer.h" +#include "snapshot/crashpad_info_client_options.h" +#include "snapshot/win/process_snapshot_win.h" +#include "util/file/file_writer.h" +#include "util/misc/tri_state.h" +#include "util/misc/uuid.h" +#include "util/win/get_function.h" +#include "util/win/handle.h" +#include "util/win/registration_protocol_win.h" +#include "util/win/safe_terminate_process.h" +#include "util/win/xp_compat.h" + +namespace crashpad { + +namespace { + +decltype(GetNamedPipeClientProcessId)* GetNamedPipeClientProcessIdFunction() { + static const auto get_named_pipe_client_process_id = + GET_FUNCTION(L"kernel32.dll", ::GetNamedPipeClientProcessId); + return get_named_pipe_client_process_id; +} + +HANDLE DuplicateEvent(HANDLE process, HANDLE event) { + HANDLE handle; + if (DuplicateHandle(GetCurrentProcess(), + event, + process, + &handle, + SYNCHRONIZE | EVENT_MODIFY_STATE, + false, + 0)) { + return handle; + } + return nullptr; +} + +} // namespace + +namespace internal { + +//! \brief Context information for the named pipe handler threads. +class PipeServiceContext { + public: + PipeServiceContext(HANDLE port, + HANDLE pipe, + ExceptionHandlerServer::Delegate* delegate, + base::Lock* clients_lock, + std::set<internal::ClientData*>* clients, + uint64_t shutdown_token) + : port_(port), + pipe_(pipe), + delegate_(delegate), + clients_lock_(clients_lock), + clients_(clients), + shutdown_token_(shutdown_token) {} + + HANDLE port() const { return port_; } + HANDLE pipe() const { return pipe_.get(); } + ExceptionHandlerServer::Delegate* delegate() const { return delegate_; } + base::Lock* clients_lock() const { return clients_lock_; } + std::set<internal::ClientData*>* clients() const { return clients_; } + uint64_t shutdown_token() const { return shutdown_token_; } + + private: + HANDLE port_; // weak + ScopedKernelHANDLE pipe_; + ExceptionHandlerServer::Delegate* delegate_; // weak + base::Lock* clients_lock_; // weak + std::set<internal::ClientData*>* clients_; // weak + uint64_t shutdown_token_; + + DISALLOW_COPY_AND_ASSIGN(PipeServiceContext); +}; + +//! \brief The context data for registered threadpool waits. +//! +//! This object must be created and destroyed on the main thread. Access must be +//! guarded by use of the lock() with the exception of the threadpool wait +//! variables which are accessed only by the main thread. +class ClientData { + public: + ClientData(HANDLE port, + ExceptionHandlerServer::Delegate* delegate, + ScopedKernelHANDLE process, + ScopedKernelHANDLE crash_dump_requested_event, + ScopedKernelHANDLE non_crash_dump_requested_event, + ScopedKernelHANDLE non_crash_dump_completed_event, + WinVMAddress crash_exception_information_address, + WinVMAddress non_crash_exception_information_address, + WinVMAddress debug_critical_section_address, + WAITORTIMERCALLBACK crash_dump_request_callback, + WAITORTIMERCALLBACK non_crash_dump_request_callback, + WAITORTIMERCALLBACK process_end_callback) + : crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE), + non_crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE), + process_end_thread_pool_wait_(INVALID_HANDLE_VALUE), + lock_(), + port_(port), + delegate_(delegate), + crash_dump_requested_event_(std::move(crash_dump_requested_event)), + non_crash_dump_requested_event_( + std::move(non_crash_dump_requested_event)), + non_crash_dump_completed_event_( + std::move(non_crash_dump_completed_event)), + process_(std::move(process)), + crash_exception_information_address_( + crash_exception_information_address), + non_crash_exception_information_address_( + non_crash_exception_information_address), + debug_critical_section_address_(debug_critical_section_address) { + RegisterThreadPoolWaits(crash_dump_request_callback, + non_crash_dump_request_callback, + process_end_callback); + } + + ~ClientData() { + // It is important that this only access the threadpool waits (it's called + // from the main thread) until the waits are unregistered, to ensure that + // any outstanding callbacks are complete. + UnregisterThreadPoolWaits(); + } + + base::Lock* lock() { return &lock_; } + HANDLE port() const { return port_; } + ExceptionHandlerServer::Delegate* delegate() const { return delegate_; } + HANDLE crash_dump_requested_event() const { + return crash_dump_requested_event_.get(); + } + HANDLE non_crash_dump_requested_event() const { + return non_crash_dump_requested_event_.get(); + } + HANDLE non_crash_dump_completed_event() const { + return non_crash_dump_completed_event_.get(); + } + WinVMAddress crash_exception_information_address() const { + return crash_exception_information_address_; + } + WinVMAddress non_crash_exception_information_address() const { + return non_crash_exception_information_address_; + } + WinVMAddress debug_critical_section_address() const { + return debug_critical_section_address_; + } + HANDLE process() const { return process_.get(); } + + private: + void RegisterThreadPoolWaits( + WAITORTIMERCALLBACK crash_dump_request_callback, + WAITORTIMERCALLBACK non_crash_dump_request_callback, + WAITORTIMERCALLBACK process_end_callback) { + if (!RegisterWaitForSingleObject(&crash_dump_request_thread_pool_wait_, + crash_dump_requested_event_.get(), + crash_dump_request_callback, + this, + INFINITE, + WT_EXECUTEDEFAULT)) { + LOG(ERROR) << "RegisterWaitForSingleObject crash dump requested"; + } + + if (!RegisterWaitForSingleObject(&non_crash_dump_request_thread_pool_wait_, + non_crash_dump_requested_event_.get(), + non_crash_dump_request_callback, + this, + INFINITE, + WT_EXECUTEDEFAULT)) { + LOG(ERROR) << "RegisterWaitForSingleObject non-crash dump requested"; + } + + if (!RegisterWaitForSingleObject(&process_end_thread_pool_wait_, + process_.get(), + process_end_callback, + this, + INFINITE, + WT_EXECUTEONLYONCE)) { + LOG(ERROR) << "RegisterWaitForSingleObject process end"; + } + } + + // This blocks until outstanding calls complete so that we know it's safe to + // delete this object. Because of this, it must be executed on the main + // thread, not a threadpool thread. + void UnregisterThreadPoolWaits() { + UnregisterWaitEx(crash_dump_request_thread_pool_wait_, + INVALID_HANDLE_VALUE); + crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE; + UnregisterWaitEx(non_crash_dump_request_thread_pool_wait_, + INVALID_HANDLE_VALUE); + non_crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE; + UnregisterWaitEx(process_end_thread_pool_wait_, INVALID_HANDLE_VALUE); + process_end_thread_pool_wait_ = INVALID_HANDLE_VALUE; + } + + // These are only accessed on the main thread. + HANDLE crash_dump_request_thread_pool_wait_; + HANDLE non_crash_dump_request_thread_pool_wait_; + HANDLE process_end_thread_pool_wait_; + + base::Lock lock_; + // Access to these fields must be guarded by lock_. + HANDLE port_; // weak + ExceptionHandlerServer::Delegate* delegate_; // weak + ScopedKernelHANDLE crash_dump_requested_event_; + ScopedKernelHANDLE non_crash_dump_requested_event_; + ScopedKernelHANDLE non_crash_dump_completed_event_; + ScopedKernelHANDLE process_; + WinVMAddress crash_exception_information_address_; + WinVMAddress non_crash_exception_information_address_; + WinVMAddress debug_critical_section_address_; + + DISALLOW_COPY_AND_ASSIGN(ClientData); +}; + +} // namespace internal + +ExceptionHandlerServer::Delegate::~Delegate() { +} + +ExceptionHandlerServer::ExceptionHandlerServer(bool persistent) + : pipe_name_(), + port_(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)), + first_pipe_instance_(), + clients_lock_(), + clients_(), + persistent_(persistent) { +} + +ExceptionHandlerServer::~ExceptionHandlerServer() { +} + +void ExceptionHandlerServer::SetPipeName(const std::wstring& pipe_name) { + DCHECK(pipe_name_.empty()); + DCHECK(!pipe_name.empty()); + + pipe_name_ = pipe_name; +} + +void ExceptionHandlerServer::InitializeWithInheritedDataForInitialClient( + const InitialClientData& initial_client_data, + Delegate* delegate) { + DCHECK(pipe_name_.empty()); + DCHECK(!first_pipe_instance_.is_valid()); + + first_pipe_instance_.reset(initial_client_data.first_pipe_instance()); + + // TODO(scottmg): Vista+. Might need to pass through or possibly find an Nt*. + size_t bytes = sizeof(wchar_t) * _MAX_PATH + sizeof(FILE_NAME_INFO); + std::unique_ptr<uint8_t[]> data(new uint8_t[bytes]); + if (!GetFileInformationByHandleEx(first_pipe_instance_.get(), + FileNameInfo, + data.get(), + static_cast<DWORD>(bytes))) { + PLOG(FATAL) << "GetFileInformationByHandleEx"; + } + FILE_NAME_INFO* file_name_info = + reinterpret_cast<FILE_NAME_INFO*>(data.get()); + pipe_name_ = + L"\\\\.\\pipe" + std::wstring(file_name_info->FileName, + file_name_info->FileNameLength / + sizeof(file_name_info->FileName[0])); + + { + base::AutoLock lock(clients_lock_); + internal::ClientData* client = new internal::ClientData( + port_.get(), + delegate, + ScopedKernelHANDLE(initial_client_data.client_process()), + ScopedKernelHANDLE(initial_client_data.request_crash_dump()), + ScopedKernelHANDLE(initial_client_data.request_non_crash_dump()), + ScopedKernelHANDLE(initial_client_data.non_crash_dump_completed()), + initial_client_data.crash_exception_information(), + initial_client_data.non_crash_exception_information(), + initial_client_data.debug_critical_section_address(), + &OnCrashDumpEvent, + &OnNonCrashDumpEvent, + &OnProcessEnd); + clients_.insert(client); + } +} + +void ExceptionHandlerServer::Run(Delegate* delegate) { + uint64_t shutdown_token = base::RandUint64(); + ScopedKernelHANDLE thread_handles[kPipeInstances]; + for (size_t i = 0; i < base::size(thread_handles); ++i) { + HANDLE pipe; + if (first_pipe_instance_.is_valid()) { + pipe = first_pipe_instance_.release(); + } else { + pipe = CreateNamedPipeInstance(pipe_name_, i == 0); + PCHECK(pipe != INVALID_HANDLE_VALUE) << "CreateNamedPipe"; + } + + // Ownership of this object (and the pipe instance) is given to the new + // thread. We close the thread handles at the end of the scope. They clean + // up the context object and the pipe instance on termination. + internal::PipeServiceContext* context = + new internal::PipeServiceContext(port_.get(), + pipe, + delegate, + &clients_lock_, + &clients_, + shutdown_token); + thread_handles[i].reset( + CreateThread(nullptr, 0, &PipeServiceProc, context, 0, nullptr)); + PCHECK(thread_handles[i].is_valid()) << "CreateThread"; + } + + delegate->ExceptionHandlerServerStarted(); + + // This is the main loop of the server. Most work is done on the threadpool, + // other than process end handling which is posted back to this main thread, + // as we must unregister the threadpool waits here. + for (;;) { + OVERLAPPED* ov = nullptr; + ULONG_PTR key = 0; + DWORD bytes = 0; + GetQueuedCompletionStatus(port_.get(), &bytes, &key, &ov, INFINITE); + if (!key) { + // Shutting down. + break; + } + + // Otherwise, this is a request to unregister and destroy the given client. + // delete'ing the ClientData blocks in UnregisterWaitEx to ensure all + // outstanding threadpool waits are complete. This is important because the + // process handle can be signalled *before* the dump request is signalled. + internal::ClientData* client = reinterpret_cast<internal::ClientData*>(key); + base::AutoLock lock(clients_lock_); + clients_.erase(client); + delete client; + if (!persistent_ && clients_.empty()) + break; + } + + // Signal to the named pipe instances that they should terminate. + for (size_t i = 0; i < base::size(thread_handles); ++i) { + ClientToServerMessage message; + memset(&message, 0, sizeof(message)); + message.type = ClientToServerMessage::kShutdown; + message.shutdown.token = shutdown_token; + ServerToClientMessage response; + SendToCrashHandlerServer(pipe_name_, + reinterpret_cast<ClientToServerMessage&>(message), + &response); + } + + for (auto& handle : thread_handles) + WaitForSingleObject(handle.get(), INFINITE); + + // Deleting ClientData does a blocking wait until the threadpool executions + // have terminated when unregistering them. + { + base::AutoLock lock(clients_lock_); + for (auto* client : clients_) + delete client; + clients_.clear(); + } +} + +void ExceptionHandlerServer::Stop() { + // Post a null key (third argument) to trigger shutdown. + PostQueuedCompletionStatus(port_.get(), 0, 0, nullptr); +} + +// This function must be called with service_context.pipe() already connected to +// a client pipe. It exchanges data with the client and adds a ClientData record +// to service_context->clients(). +// +// static +bool ExceptionHandlerServer::ServiceClientConnection( + const internal::PipeServiceContext& service_context) { + ClientToServerMessage message; + + if (!LoggingReadFileExactly( + service_context.pipe(), &message, sizeof(message))) + return false; + + switch (message.type) { + case ClientToServerMessage::kShutdown: { + if (message.shutdown.token != service_context.shutdown_token()) { + LOG(ERROR) << "forged shutdown request, got: " + << message.shutdown.token; + return false; + } + ServerToClientMessage shutdown_response = {}; + LoggingWriteFile(service_context.pipe(), + &shutdown_response, + sizeof(shutdown_response)); + return true; + } + + case ClientToServerMessage::kPing: { + // No action required, the fact that the message was processed is + // sufficient. + ServerToClientMessage shutdown_response = {}; + LoggingWriteFile(service_context.pipe(), + &shutdown_response, + sizeof(shutdown_response)); + return false; + } + + case ClientToServerMessage::kRegister: + // Handled below. + break; + + default: + LOG(ERROR) << "unhandled message type: " << message.type; + return false; + } + + if (message.registration.version != RegistrationRequest::kMessageVersion) { + LOG(ERROR) << "unexpected version. got: " << message.registration.version + << " expecting: " << RegistrationRequest::kMessageVersion; + return false; + } + + decltype(GetNamedPipeClientProcessId)* get_named_pipe_client_process_id = + GetNamedPipeClientProcessIdFunction(); + if (get_named_pipe_client_process_id) { + // GetNamedPipeClientProcessId is only available on Vista+. + DWORD real_pid = 0; + if (get_named_pipe_client_process_id(service_context.pipe(), &real_pid) && + message.registration.client_process_id != real_pid) { + LOG(ERROR) << "forged client pid, real pid: " << real_pid + << ", got: " << message.registration.client_process_id; + return false; + } + } + + // We attempt to open the process as us. This is the main case that should + // almost always succeed as the server will generally be more privileged. If + // we're running as a different user, it may be that we will fail to open + // the process, but the client will be able to, so we make a second attempt + // having impersonated the client. + HANDLE client_process = OpenProcess( + kXPProcessAllAccess, false, message.registration.client_process_id); + if (!client_process) { + if (!ImpersonateNamedPipeClient(service_context.pipe())) { + PLOG(ERROR) << "ImpersonateNamedPipeClient"; + return false; + } + client_process = OpenProcess( + kXPProcessAllAccess, false, message.registration.client_process_id); + PCHECK(RevertToSelf()); + if (!client_process) { + LOG(ERROR) << "failed to open " << message.registration.client_process_id; + return false; + } + } + + internal::ClientData* client; + { + base::AutoLock lock(*service_context.clients_lock()); + client = new internal::ClientData( + service_context.port(), + service_context.delegate(), + ScopedKernelHANDLE(client_process), + ScopedKernelHANDLE( + CreateEvent(nullptr, false /* auto reset */, false, nullptr)), + ScopedKernelHANDLE( + CreateEvent(nullptr, false /* auto reset */, false, nullptr)), + ScopedKernelHANDLE( + CreateEvent(nullptr, false /* auto reset */, false, nullptr)), + message.registration.crash_exception_information, + message.registration.non_crash_exception_information, + message.registration.critical_section_address, + &OnCrashDumpEvent, + &OnNonCrashDumpEvent, + &OnProcessEnd); + service_context.clients()->insert(client); + } + + // Duplicate the events back to the client so they can request a dump. + ServerToClientMessage response; + response.registration.request_crash_dump_event = + HandleToInt(DuplicateEvent( + client->process(), client->crash_dump_requested_event())); + response.registration.request_non_crash_dump_event = + HandleToInt(DuplicateEvent( + client->process(), client->non_crash_dump_requested_event())); + response.registration.non_crash_dump_completed_event = + HandleToInt(DuplicateEvent( + client->process(), client->non_crash_dump_completed_event())); + + if (!LoggingWriteFile(service_context.pipe(), &response, sizeof(response))) + return false; + + return false; +} + +// static +DWORD __stdcall ExceptionHandlerServer::PipeServiceProc(void* ctx) { + internal::PipeServiceContext* service_context = + reinterpret_cast<internal::PipeServiceContext*>(ctx); + DCHECK(service_context); + + for (;;) { + bool ret = !!ConnectNamedPipe(service_context->pipe(), nullptr); + if (!ret && GetLastError() != ERROR_PIPE_CONNECTED) { + PLOG(ERROR) << "ConnectNamedPipe"; + } else if (ServiceClientConnection(*service_context)) { + break; + } + DisconnectNamedPipe(service_context->pipe()); + } + + delete service_context; + + return 0; +} + +// static +void __stdcall ExceptionHandlerServer::OnCrashDumpEvent(void* ctx, BOOLEAN) { + // This function is executed on the thread pool. + internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx); + base::AutoLock lock(*client->lock()); + + // Capture the exception. + unsigned int exit_code = client->delegate()->ExceptionHandlerServerException( + client->process(), + client->crash_exception_information_address(), + client->debug_critical_section_address()); + + SafeTerminateProcess(client->process(), exit_code); +} + +// static +void __stdcall ExceptionHandlerServer::OnNonCrashDumpEvent(void* ctx, BOOLEAN) { + // This function is executed on the thread pool. + internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx); + base::AutoLock lock(*client->lock()); + + // Capture the exception. + client->delegate()->ExceptionHandlerServerException( + client->process(), + client->non_crash_exception_information_address(), + client->debug_critical_section_address()); + + bool result = !!SetEvent(client->non_crash_dump_completed_event()); + PLOG_IF(ERROR, !result) << "SetEvent"; +} + +// static +void __stdcall ExceptionHandlerServer::OnProcessEnd(void* ctx, BOOLEAN) { + // This function is executed on the thread pool. + internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx); + base::AutoLock lock(*client->lock()); + + // Post back to the main thread to have it delete this client record. + PostQueuedCompletionStatus(client->port(), 0, ULONG_PTR(client), nullptr); +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/exception_handler_server.h b/src/third_party/crashpad/util/win/exception_handler_server.h new file mode 100644 index 0000000..994cdba --- /dev/null +++ b/src/third_party/crashpad/util/win/exception_handler_server.h
@@ -0,0 +1,140 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_EXCEPTION_HANDLER_SERVER_H_ +#define CRASHPAD_UTIL_WIN_EXCEPTION_HANDLER_SERVER_H_ + +#include <set> +#include <string> + +#include "base/macros.h" +#include "base/synchronization/lock.h" +#include "util/file/file_io.h" +#include "util/win/address_types.h" +#include "util/win/initial_client_data.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { + +namespace internal { +class PipeServiceContext; +class ClientData; +} // namespace internal + +//! \brief Runs the main exception-handling server in Crashpad's handler +//! process. +class ExceptionHandlerServer { + public: + class Delegate { + public: + //! \brief Called when the server has created the named pipe connection + //! points and is ready to service requests. + virtual void ExceptionHandlerServerStarted() = 0; + + //! \brief Called when the client has signalled that it has encountered an + //! exception and so wants a crash dump to be taken. + //! + //! \param[in] process A handle to the client process. Ownership of the + //! lifetime of this handle is not passed to the delegate. + //! \param[in] exception_information_address The address in the client's + //! address space of an ExceptionInformation structure. + //! \param[in] debug_critical_section_address The address in the client's + //! address space of a `CRITICAL_SECTION` allocated with a valid + //! `.DebugInfo` field, or `0` if unavailable. + //! \return The exit code that should be used when terminating the client + //! process. + virtual unsigned int ExceptionHandlerServerException( + HANDLE process, + WinVMAddress exception_information_address, + WinVMAddress debug_critical_section_address) = 0; + + protected: + ~Delegate(); + }; + + //! \brief Constructs the exception handling server. + //! + //! \param[in] persistent `true` if Run() should not return until Stop() is + //! called. If `false`, Run() will return when all clients have exited, + //! although Run() will always wait for the first client to connect. + explicit ExceptionHandlerServer(bool persistent); + + ~ExceptionHandlerServer(); + + //! \brief Sets the pipe name to listen for client registrations on. + //! + //! This method, or InitializeWithInheritedDataForInitialClient(), must be + //! called before Run(). + //! + //! \param[in] pipe_name The name of the pipe to listen on. Must be of the + //! form "\\.\pipe\<some_name>". + void SetPipeName(const std::wstring& pipe_name); + + //! \brief Sets the pipe to listen for client registrations on, providing + //! the first precreated instance. + //! + //! This method, or SetPipeName(), must be called before Run(). All of these + //! parameters are generally created in a parent process that launches the + //! handler. For more details see the Windows implementation of + //! CrashpadClient. + //! + //! \sa CrashpadClient + //! \sa RegistrationRequest + //! + //! \param[in] initial_client_data The handles and addresses of data inherited + //! from a parent process needed to initialize and register the first + //! client. Ownership of these handles is taken. + //! \param[in] delegate The interface to which the exceptions are delegated + //! when they are caught in Run(). Ownership is not transferred. + void InitializeWithInheritedDataForInitialClient( + const InitialClientData& initial_client_data, + Delegate* delegate); + + //! \brief Runs the exception-handling server. + //! + //! \param[in] delegate The interface to which the exceptions are delegated + //! when they are caught in Run(). Ownership is not transferred. + void Run(Delegate* delegate); + + //! \brief Stops the exception-handling server. Returns immediately. The + //! object must not be destroyed until Run() returns. + void Stop(); + + //! \brief The number of server-side pipe instances that the exception handler + //! server creates to listen for connections from clients. + static const size_t kPipeInstances = 2; + + private: + static bool ServiceClientConnection( + const internal::PipeServiceContext& service_context); + static DWORD __stdcall PipeServiceProc(void* ctx); + static void __stdcall OnCrashDumpEvent(void* ctx, BOOLEAN); + static void __stdcall OnNonCrashDumpEvent(void* ctx, BOOLEAN); + static void __stdcall OnProcessEnd(void* ctx, BOOLEAN); + + std::wstring pipe_name_; + ScopedKernelHANDLE port_; + ScopedFileHandle first_pipe_instance_; + + base::Lock clients_lock_; + std::set<internal::ClientData*> clients_; + + bool persistent_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_EXCEPTION_HANDLER_SERVER_H_
diff --git a/src/third_party/crashpad/util/win/exception_handler_server_test.cc b/src/third_party/crashpad/util/win/exception_handler_server_test.cc new file mode 100644 index 0000000..ce31677 --- /dev/null +++ b/src/third_party/crashpad/util/win/exception_handler_server_test.cc
@@ -0,0 +1,213 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/exception_handler_server.h" + +#include <windows.h> +#include <sys/types.h> + +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/strings/utf_string_conversions.h" +#include "client/crashpad_client.h" +#include "gtest/gtest.h" +#include "test/win/win_child_process.h" +#include "util/thread/thread.h" +#include "util/win/address_types.h" +#include "util/win/registration_protocol_win.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { +namespace test { +namespace { + +// Runs the ExceptionHandlerServer on a background thread. +class RunServerThread : public Thread { + public: + // Instantiates a thread which will invoke server->Run(delegate). + RunServerThread(ExceptionHandlerServer* server, + ExceptionHandlerServer::Delegate* delegate) + : server_(server), delegate_(delegate) {} + ~RunServerThread() override {} + + private: + // Thread: + void ThreadMain() override { server_->Run(delegate_); } + + ExceptionHandlerServer* server_; + ExceptionHandlerServer::Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(RunServerThread); +}; + +class TestDelegate : public ExceptionHandlerServer::Delegate { + public: + explicit TestDelegate(HANDLE server_ready) : server_ready_(server_ready) {} + ~TestDelegate() {} + + void ExceptionHandlerServerStarted() override { + SetEvent(server_ready_); + } + unsigned int ExceptionHandlerServerException( + HANDLE process, + WinVMAddress exception_information_address, + WinVMAddress debug_critical_section_address) override { + return 0; + } + + void WaitForStart() { WaitForSingleObject(server_ready_, INFINITE); } + + private: + HANDLE server_ready_; // weak + + DISALLOW_COPY_AND_ASSIGN(TestDelegate); +}; + +class ExceptionHandlerServerTest : public testing::Test { + public: + ExceptionHandlerServerTest() + : server_(true), + pipe_name_(L"\\\\.\\pipe\\test_name"), + server_ready_(CreateEvent(nullptr, false, false, nullptr)), + delegate_(server_ready_.get()), + server_thread_(&server_, &delegate_) { + server_.SetPipeName(pipe_name_); + } + + TestDelegate& delegate() { return delegate_; } + ExceptionHandlerServer& server() { return server_; } + Thread& server_thread() { return server_thread_; } + const std::wstring& pipe_name() const { return pipe_name_; } + + private: + ExceptionHandlerServer server_; + std::wstring pipe_name_; + ScopedKernelHANDLE server_ready_; + TestDelegate delegate_; + RunServerThread server_thread_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest); +}; + +// During destruction, ensures that the server is stopped and the background +// thread joined. +class ScopedStopServerAndJoinThread { + public: + ScopedStopServerAndJoinThread(ExceptionHandlerServer* server, Thread* thread) + : server_(server), thread_(thread) {} + ~ScopedStopServerAndJoinThread() { + server_->Stop(); + thread_->Join(); + } + + private: + ExceptionHandlerServer* server_; + Thread* thread_; + DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread); +}; + +TEST_F(ExceptionHandlerServerTest, Instantiate) { +} + +TEST_F(ExceptionHandlerServerTest, StartAndStop) { + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); +} + +TEST_F(ExceptionHandlerServerTest, StopWhileConnected) { + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); + CrashpadClient client; + client.SetHandlerIPCPipe(pipe_name()); + // Leaving this scope causes the server to be stopped, while the connection + // is still open. +} + +std::wstring ReadWString(FileHandle handle) { + size_t length = 0; + EXPECT_TRUE(LoggingReadFileExactly(handle, &length, sizeof(length))); + std::wstring str(length, L'\0'); + if (length > 0) { + EXPECT_TRUE( + LoggingReadFileExactly(handle, &str[0], length * sizeof(str[0]))); + } + return str; +} + +void WriteWString(FileHandle handle, const std::wstring& str) { + size_t length = str.size(); + EXPECT_TRUE(LoggingWriteFile(handle, &length, sizeof(length))); + if (length > 0) { + EXPECT_TRUE(LoggingWriteFile(handle, &str[0], length * sizeof(str[0]))); + } +} + +class TestClient final : public WinChildProcess { + public: + TestClient() : WinChildProcess() {} + + ~TestClient() {} + + private: + int Run() override { + std::wstring pipe_name = ReadWString(ReadPipeHandle()); + CrashpadClient client; + if (!client.SetHandlerIPCPipe(pipe_name)) { + ADD_FAILURE(); + return EXIT_FAILURE; + } + WriteWString(WritePipeHandle(), L"OK"); + return EXIT_SUCCESS; + } + + DISALLOW_COPY_AND_ASSIGN(TestClient); +}; + +TEST_F(ExceptionHandlerServerTest, MultipleConnections) { + WinChildProcess::EntryPoint<TestClient>(); + + std::unique_ptr<WinChildProcess::Handles> handles_1 = + WinChildProcess::Launch(); + std::unique_ptr<WinChildProcess::Handles> handles_2 = + WinChildProcess::Launch(); + std::unique_ptr<WinChildProcess::Handles> handles_3 = + WinChildProcess::Launch(); + + // Must ensure the delegate outlasts the server. + { + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); + + // Tell all the children where to connect. + WriteWString(handles_1->write.get(), pipe_name()); + WriteWString(handles_2->write.get(), pipe_name()); + WriteWString(handles_3->write.get(), pipe_name()); + + ASSERT_EQ(ReadWString(handles_3->read.get()), L"OK"); + ASSERT_EQ(ReadWString(handles_2->read.get()), L"OK"); + ASSERT_EQ(ReadWString(handles_1->read.get()), L"OK"); + } +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/get_function.cc b/src/third_party/crashpad/util/win/get_function.cc new file mode 100644 index 0000000..d498d30 --- /dev/null +++ b/src/third_party/crashpad/util/win/get_function.cc
@@ -0,0 +1,44 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/get_function.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace crashpad { +namespace internal { + +FARPROC GetFunctionInternal( + const wchar_t* library, const char* function, bool required) { + HMODULE module = LoadLibrary(library); + DPCHECK(!required || module) << "LoadLibrary " << base::UTF16ToUTF8(library); + if (!module) { + return nullptr; + } + + // Strip off any leading :: that may have come from stringifying the + // function’s name. + if (function[0] == ':' && function[1] == ':' && + function[2] && function[2] != ':') { + function += 2; + } + + FARPROC proc = GetProcAddress(module, function); + DPCHECK(!required || proc) << "GetProcAddress " << function; + return proc; +} + +} // namespace internal +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/get_function.h b/src/third_party/crashpad/util/win/get_function.h new file mode 100644 index 0000000..50e5f15 --- /dev/null +++ b/src/third_party/crashpad/util/win/get_function.h
@@ -0,0 +1,121 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_GET_FUNCTION_H_ +#define CRASHPAD_UTIL_WIN_GET_FUNCTION_H_ + +#include <windows.h> + +//! \file + +namespace crashpad { +namespace internal { + +//! \brief Returns a function pointer to a named function in a library. +//! +//! Do not call this directly, use the GET_FUNCTION() or GET_FUNCTION_REQUIRED() +//! macros instead. +//! +//! This accesses \a library by calling `LoadLibrary()` and is subject to the +//! same restrictions as that function. Notably, it can’t be used from a +//! `DllMain()` entry point. +//! +//! \param[in] library The library to search in. +//! \param[in] function The function to search for. If a leading `::` is +//! present, it will be stripped. +//! \param[in] required If `true`, require the function to resolve by `DCHECK`. +//! +//! \return A pointer to the requested function on success. If \a required is +//! `true`, triggers a `DCHECK` assertion on failure, otherwise, `nullptr` +//! on failure. +FARPROC GetFunctionInternal( + const wchar_t* library, const char* function, bool required); + +//! \copydoc GetFunctionInternal +template <typename FunctionType> +FunctionType* GetFunction( + const wchar_t* library, const char* function, bool required) { + return reinterpret_cast<FunctionType*>( + internal::GetFunctionInternal(library, function, required)); +} + +} // namespace internal +} // namespace crashpad + +//! \brief Returns a function pointer to a named function in a library without +//! requiring that it be found. +//! +//! If the library or function cannot be found, this will return `nullptr`. This +//! macro is intended to be used to access functions that may not be available +//! at runtime. +//! +//! This macro returns a properly-typed function pointer. It is expected to be +//! used in this way: +//! \code +//! static const auto get_named_pipe_client_process_id = +//! GET_FUNCTION(L"kernel32.dll", ::GetNamedPipeClientProcessId); +//! if (get_named_pipe_client_process_id) { +//! BOOL rv = get_named_pipe_client_process_id(pipe, &client_process_id); +//! } +//! \endcode +//! +//! This accesses \a library by calling `LoadLibrary()` and is subject to the +//! same restrictions as that function. Notably, it can’t be used from a +//! `DllMain()` entry point. +//! +//! \param[in] library The library to search in. +//! \param[in] function The function to search for. A leading `::` is +//! recommended when a wrapper function of the same name is present. +//! +//! \return A pointer to the requested function on success, or `nullptr` on +//! failure. +//! +//! \sa GET_FUNCTION_REQUIRED +#define GET_FUNCTION(library, function) \ + crashpad::internal::GetFunction<decltype(function)>( \ + library, #function, false) + +//! \brief Returns a function pointer to a named function in a library, +//! requiring that it be found. +//! +//! If the library or function cannot be found, this will trigger a `DCHECK` +//! assertion. This macro is intended to be used to access functions that are +//! always expected to be available at runtime but which are not present in any +//! import library. +//! +//! This macro returns a properly-typed function pointer. It is expected to be +//! used in this way: +//! \code +//! static const auto nt_query_object = +//! GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryObject); +//! NTSTATUS status = +//! nt_query_object(handle, type, &info, info_length, &return_length); +//! \endcode +//! +//! This accesses \a library by calling `LoadLibrary()` and is subject to the +//! same restrictions as that function. Notably, it can’t be used from a +//! `DllMain()` entry point. +//! +//! \param[in] library The library to search in. +//! \param[in] function The function to search for. A leading `::` is +//! recommended when a wrapper function of the same name is present. +//! +//! \return A pointer to the requested function. +//! +//! \sa GET_FUNCTION +#define GET_FUNCTION_REQUIRED(library, function) \ + crashpad::internal::GetFunction<decltype(function)>( \ + library, #function, true) + +#endif // CRASHPAD_UTIL_WIN_GET_FUNCTION_H_
diff --git a/src/third_party/crashpad/util/win/get_function_test.cc b/src/third_party/crashpad/util/win/get_function_test.cc new file mode 100644 index 0000000..a54ce7d --- /dev/null +++ b/src/third_party/crashpad/util/win/get_function_test.cc
@@ -0,0 +1,78 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/get_function.h" + +#include <windows.h> +#include <winternl.h> + +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(GetFunction, GetFunction) { + // Check equivalence of GET_FUNCTION_REQUIRED() with functions that are + // available in the SDK normally. + EXPECT_EQ(GET_FUNCTION_REQUIRED(L"kernel32.dll", GetProcAddress), + &GetProcAddress); + EXPECT_EQ(GET_FUNCTION_REQUIRED(L"kernel32.dll", LoadLibraryW), + &LoadLibraryW); + + // Make sure that a function pointer retrieved by GET_FUNCTION_REQUIRED() can + // be called and that it works correctly. + const auto get_current_process_id = + GET_FUNCTION_REQUIRED(L"kernel32.dll", GetCurrentProcessId); + EXPECT_EQ(get_current_process_id, &GetCurrentProcessId); + ASSERT_TRUE(get_current_process_id); + EXPECT_EQ(get_current_process_id(), GetCurrentProcessId()); + + // GET_FUNCTION_REQUIRED() and GET_FUNCTION() should behave identically when + // the function is present. + EXPECT_EQ(GET_FUNCTION(L"kernel32.dll", GetCurrentProcessId), + get_current_process_id); + + // Using a leading :: should also work. + EXPECT_EQ(GET_FUNCTION(L"kernel32.dll", ::GetCurrentProcessId), + get_current_process_id); + EXPECT_EQ(GET_FUNCTION_REQUIRED(L"kernel32.dll", ::GetCurrentProcessId), + get_current_process_id); + + // Try a function that’s declared in the SDK’s headers but that has no import + // library. + EXPECT_TRUE(GET_FUNCTION_REQUIRED(L"ntdll.dll", RtlNtStatusToDosError)); + + // GetNamedPipeClientProcessId() is only available on Vista and later. + const auto get_named_pipe_client_process_id = + GET_FUNCTION(L"kernel32.dll", GetNamedPipeClientProcessId); + const DWORD version = GetVersion(); + const DWORD major_version = LOBYTE(LOWORD(version)); + EXPECT_EQ(get_named_pipe_client_process_id != nullptr, major_version >= 6); + + // Test that GET_FUNCTION() can fail by trying a nonexistent library and a + // symbol that doesn’t exist in the specified library. + EXPECT_FALSE(GET_FUNCTION(L"not_a_real_library.dll", TerminateProcess)); + EXPECT_FALSE(GET_FUNCTION(L"ntdll.dll", TerminateProcess)); + EXPECT_FALSE(GET_FUNCTION(L"not_a_real_library.dll", ::TerminateProcess)); + EXPECT_FALSE(GET_FUNCTION(L"ntdll.dll", ::TerminateProcess)); + + // Here it is! + EXPECT_TRUE(GET_FUNCTION(L"kernel32.dll", TerminateProcess)); + EXPECT_TRUE(GET_FUNCTION(L"kernel32.dll", ::TerminateProcess)); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/get_module_information.cc b/src/third_party/crashpad/util/win/get_module_information.cc new file mode 100644 index 0000000..850bfe1 --- /dev/null +++ b/src/third_party/crashpad/util/win/get_module_information.cc
@@ -0,0 +1,34 @@ +// Copyright 2016 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/get_module_information.h" + +#include "util/win/get_function.h" + +namespace crashpad { + +BOOL CrashpadGetModuleInformation(HANDLE process, + HMODULE module, + MODULEINFO* module_info, + DWORD cb) { +#if PSAPI_VERSION == 1 + static const auto get_module_information = + GET_FUNCTION_REQUIRED(L"psapi.dll", GetModuleInformation); + return get_module_information(process, module, module_info, cb); +#elif PSAPI_VERSION == 2 + return GetModuleInformation(process, module, module_info, cb); +#endif +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/get_module_information.h b/src/third_party/crashpad/util/win/get_module_information.h new file mode 100644 index 0000000..8fce035 --- /dev/null +++ b/src/third_party/crashpad/util/win/get_module_information.h
@@ -0,0 +1,35 @@ +// Copyright 2016 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_GET_MODULE_INFORMATION_H_ +#define CRASHPAD_UTIL_WIN_GET_MODULE_INFORMATION_H_ + +#include <windows.h> + +#ifndef PSAPI_VERSION +#define PSAPI_VERSION 2 +#endif +#include <psapi.h> + +namespace crashpad { + +//! \brief Proxy function for `GetModuleInformation()`. +BOOL CrashpadGetModuleInformation(HANDLE process, + HMODULE module, + MODULEINFO* module_info, + DWORD cb); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_GET_MODULE_INFORMATION_H_
diff --git a/src/third_party/crashpad/util/win/handle.cc b/src/third_party/crashpad/util/win/handle.cc new file mode 100644 index 0000000..1744ecc --- /dev/null +++ b/src/third_party/crashpad/util/win/handle.cc
@@ -0,0 +1,37 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/handle.h" + +#include <stdint.h> + +#include "base/numerics/safe_conversions.h" +#include "util/misc/from_pointer_cast.h" + +namespace crashpad { + +// These functions use “int” for the 32-bit integer handle type because +// sign-extension needs to work correctly. INVALID_HANDLE_VALUE is defined as +// ((HANDLE)(LONG_PTR)-1), and this needs to round-trip through an integer and +// back to the same HANDLE value. + +int HandleToInt(HANDLE handle) { + return FromPointerCast<int>(handle); +} + +HANDLE IntToHandle(int handle_int) { + return reinterpret_cast<HANDLE>(static_cast<intptr_t>(handle_int)); +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/handle.h b/src/third_party/crashpad/util/win/handle.h new file mode 100644 index 0000000..951302e --- /dev/null +++ b/src/third_party/crashpad/util/win/handle.h
@@ -0,0 +1,65 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_HANDLE_TO_INT_H_ +#define CRASHPAD_UTIL_WIN_HANDLE_TO_INT_H_ + +#include <windows.h> + +namespace crashpad { + +//! \brief Converts a `HANDLE` to an `int`. +//! +//! `HANDLE` is a `typedef` for `void *`, but kernel `HANDLE` values aren’t +//! pointers to anything. Only 32 bits of kernel `HANDLE`s are significant, even +//! in 64-bit processes on 64-bit operating systems. See <a +//! href="https://msdn.microsoft.com/library/aa384203.aspx">Interprocess +//! Communication Between 32-bit and 64-bit Applications</a>. +//! +//! This function safely converts a kernel `HANDLE` to an `int` similarly to a +//! cast operation. It checks that the operation can be performed safely, and +//! aborts execution if it cannot. +//! +//! \param[in] handle The kernel `HANDLE` to convert. +//! +//! \return An equivalent `int`, truncated (if necessary) from \a handle. If +//! truncation would have resulted in an `int` that could not be converted +//! back to \a handle, aborts execution. +//! +//! \sa IntToHandle() +int HandleToInt(HANDLE handle); + +//! \brief Converts an `int` to an `HANDLE`. +//! +//! `HANDLE` is a `typedef` for `void *`, but kernel `HANDLE` values aren’t +//! pointers to anything. Only 32 bits of kernel `HANDLE`s are significant, even +//! in 64-bit processes on 64-bit operating systems. See <a +//! href="https://msdn.microsoft.com/library/aa384203.aspx">Interprocess +//! Communication Between 32-bit and 64-bit Applications</a>. +//! +//! This function safely convert an `int` to a kernel `HANDLE` similarly to a +//! cast operation. +//! +//! \param[in] handle_int The `int` to convert. This must have been produced by +//! HandleToInt(), possibly in a different process. +//! +//! \return An equivalent kernel `HANDLE`, sign-extended (if necessary) from \a +//! handle_int. +//! +//! \sa HandleToInt() +HANDLE IntToHandle(int handle_int); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_HANDLE_TO_INT_H_
diff --git a/src/third_party/crashpad/util/win/handle_test.cc b/src/third_party/crashpad/util/win/handle_test.cc new file mode 100644 index 0000000..5898943 --- /dev/null +++ b/src/third_party/crashpad/util/win/handle_test.cc
@@ -0,0 +1,53 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/handle.h" + +#include <stdint.h> + +#include <limits> + +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(Handle, HandleToInt) { + EXPECT_EQ(HandleToInt(nullptr), 0); + EXPECT_EQ(HandleToInt(INVALID_HANDLE_VALUE), -1); + EXPECT_EQ(HandleToInt(reinterpret_cast<HANDLE>(1)), 1); + EXPECT_EQ(HandleToInt(reinterpret_cast<HANDLE>( + static_cast<intptr_t>(std::numeric_limits<int>::max()))), + std::numeric_limits<int>::max()); + EXPECT_EQ(HandleToInt(reinterpret_cast<HANDLE>( + static_cast<intptr_t>(std::numeric_limits<int>::min()))), + std::numeric_limits<int>::min()); +} + +TEST(Handle, IntToHandle) { + EXPECT_EQ(IntToHandle(0), nullptr); + EXPECT_EQ(IntToHandle(-1), INVALID_HANDLE_VALUE); + EXPECT_EQ(IntToHandle(1), reinterpret_cast<HANDLE>(1)); + EXPECT_EQ(IntToHandle(std::numeric_limits<int>::max()), + reinterpret_cast<HANDLE>( + static_cast<intptr_t>(std::numeric_limits<int>::max()))); + EXPECT_EQ(IntToHandle(std::numeric_limits<int>::min()), + reinterpret_cast<HANDLE>( + static_cast<intptr_t>(std::numeric_limits<int>::min()))); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/initial_client_data.cc b/src/third_party/crashpad/util/win/initial_client_data.cc new file mode 100644 index 0000000..cbeecf1 --- /dev/null +++ b/src/third_party/crashpad/util/win/initial_client_data.cc
@@ -0,0 +1,113 @@ +// Copyright 2016 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/initial_client_data.h" + +#include <vector> + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "util/stdlib/string_number_conversion.h" +#include "util/string/split_string.h" +#include "util/win/handle.h" + +namespace crashpad { + +namespace { + +bool HandleFromString(const std::string& str, HANDLE* handle) { + unsigned int handle_uint; + if (!StringToNumber(str, &handle_uint) || + (*handle = IntToHandle(handle_uint)) == INVALID_HANDLE_VALUE) { + LOG(ERROR) << "could not convert '" << str << "' to HANDLE"; + return false; + } + return true; +} + +bool AddressFromString(const std::string& str, WinVMAddress* address) { + if (!StringToNumber(str, address)) { + LOG(ERROR) << "could not convert '" << str << "' to WinVMAddress"; + return false; + } + return true; +} + +} // namespace + +InitialClientData::InitialClientData() + : crash_exception_information_(0), + non_crash_exception_information_(0), + debug_critical_section_address_(0), + request_crash_dump_(nullptr), + request_non_crash_dump_(nullptr), + non_crash_dump_completed_(nullptr), + first_pipe_instance_(INVALID_HANDLE_VALUE), + client_process_(nullptr), + is_valid_(false) {} + +InitialClientData::InitialClientData( + HANDLE request_crash_dump, + HANDLE request_non_crash_dump, + HANDLE non_crash_dump_completed, + HANDLE first_pipe_instance, + HANDLE client_process, + WinVMAddress crash_exception_information, + WinVMAddress non_crash_exception_information, + WinVMAddress debug_critical_section_address) + : crash_exception_information_(crash_exception_information), + non_crash_exception_information_(non_crash_exception_information), + debug_critical_section_address_(debug_critical_section_address), + request_crash_dump_(request_crash_dump), + request_non_crash_dump_(request_non_crash_dump), + non_crash_dump_completed_(non_crash_dump_completed), + first_pipe_instance_(first_pipe_instance), + client_process_(client_process), + is_valid_(true) {} + +bool InitialClientData::InitializeFromString(const std::string& str) { + std::vector<std::string> parts(SplitString(str, ',')); + if (parts.size() != 8) { + LOG(ERROR) << "expected 8 comma separated arguments"; + return false; + } + + if (!HandleFromString(parts[0], &request_crash_dump_) || + !HandleFromString(parts[1], &request_non_crash_dump_) || + !HandleFromString(parts[2], &non_crash_dump_completed_) || + !HandleFromString(parts[3], &first_pipe_instance_) || + !HandleFromString(parts[4], &client_process_) || + !AddressFromString(parts[5], &crash_exception_information_) || + !AddressFromString(parts[6], &non_crash_exception_information_) || + !AddressFromString(parts[7], &debug_critical_section_address_)) { + return false; + } + + is_valid_ = true; + return true; +} + +std::string InitialClientData::StringRepresentation() const { + return base::StringPrintf("0x%x,0x%x,0x%x,0x%x,0x%x,0x%I64x,0x%I64x,0x%I64x", + HandleToInt(request_crash_dump_), + HandleToInt(request_non_crash_dump_), + HandleToInt(non_crash_dump_completed_), + HandleToInt(first_pipe_instance_), + HandleToInt(client_process_), + crash_exception_information_, + non_crash_exception_information_, + debug_critical_section_address_); +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/initial_client_data.h b/src/third_party/crashpad/util/win/initial_client_data.h new file mode 100644 index 0000000..f686959 --- /dev/null +++ b/src/third_party/crashpad/util/win/initial_client_data.h
@@ -0,0 +1,116 @@ +// Copyright 2016 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_INITIAL_CLIENT_DATA_H_ +#define CRASHPAD_UTIL_WIN_INITIAL_CLIENT_DATA_H_ + +#include <windows.h> + +#include <string> + +#include "base/macros.h" +#include "util/win/address_types.h" + +namespace crashpad { + +//! \brief A container for the data associated with the `--initial-client-data` +//! method for initializing the handler process on Windows. +class InitialClientData { + public: + //! \brief Constructs an unintialized instance to be used with + //! InitializeFromString(). + InitialClientData(); + + //! \brief Constructs an instance of InitialClientData. This object does not + //! take ownership of any of the referenced HANDLEs. + //! + //! \param[in] request_crash_dump An event signalled from the client on crash. + //! \param[in] request_non_crash_dump An event signalled from the client when + //! it would like a dump to be taken, but allowed to continue afterwards. + //! \param[in] non_crash_dump_completed An event signalled from the handler to + //! tell the client that the non-crash dump has completed, and it can + //! continue execution. + //! \param[in] first_pipe_instance The server end and first instance of a pipe + //! that will be used for communication with all other clients after this + //! initial one. + //! \param[in] client_process A process handle for the client being + //! registered. + //! \param[in] crash_exception_information The address, in the client's + //! address space, of an ExceptionInformation structure, used when + //! handling a crash dump request. + //! \param[in] non_crash_exception_information The address, in the client's + //! address space, of an ExceptionInformation structure, used when + //! handling a non-crashing dump request. + //! \param[in] debug_critical_section_address The address, in the client + //! process's address space, of a `CRITICAL_SECTION` allocated with a + //! valid .DebugInfo field. This can be accomplished by using + //! InitializeCriticalSectionWithDebugInfoIfPossible() or equivalent. This + //! value can be `0`, however then limited lock data will be available in + //! minidumps. + InitialClientData(HANDLE request_crash_dump, + HANDLE request_non_crash_dump, + HANDLE non_crash_dump_completed, + HANDLE first_pipe_instance, + HANDLE client_process, + WinVMAddress crash_exception_information, + WinVMAddress non_crash_exception_information, + WinVMAddress debug_critical_section_address); + + //! \brief Returns whether the object has been initialized successfully. + bool IsValid() const { return is_valid_; } + + //! Initializes this object from a string representation presumed to have been + //! created by StringRepresentation(). + //! + //! \param[in] str The output of StringRepresentation(). + //! + //! \return `true` on success, or `false` with a message logged on failure. + bool InitializeFromString(const std::string& str); + + //! \brief Returns a string representation of the data of this object, + //! suitable for passing on the command line. + std::string StringRepresentation() const; + + HANDLE request_crash_dump() const { return request_crash_dump_; } + HANDLE request_non_crash_dump() const { return request_non_crash_dump_; } + HANDLE non_crash_dump_completed() const { return non_crash_dump_completed_; } + HANDLE first_pipe_instance() const { return first_pipe_instance_; } + HANDLE client_process() const { return client_process_; } + WinVMAddress crash_exception_information() const { + return crash_exception_information_; + } + WinVMAddress non_crash_exception_information() const { + return non_crash_exception_information_; + } + WinVMAddress debug_critical_section_address() const { + return debug_critical_section_address_; + } + + private: + WinVMAddress crash_exception_information_; + WinVMAddress non_crash_exception_information_; + WinVMAddress debug_critical_section_address_; + HANDLE request_crash_dump_; + HANDLE request_non_crash_dump_; + HANDLE non_crash_dump_completed_; + HANDLE first_pipe_instance_; + HANDLE client_process_; + bool is_valid_; + + DISALLOW_COPY_AND_ASSIGN(InitialClientData); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_INITIAL_CLIENT_DATA_H_
diff --git a/src/third_party/crashpad/util/win/initial_client_data_test.cc b/src/third_party/crashpad/util/win/initial_client_data_test.cc new file mode 100644 index 0000000..2121249 --- /dev/null +++ b/src/third_party/crashpad/util/win/initial_client_data_test.cc
@@ -0,0 +1,73 @@ +// Copyright 2016 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/initial_client_data.h" + +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(InitialClientData, Validity) { + InitialClientData icd1; + EXPECT_FALSE(icd1.IsValid()); + + InitialClientData icd2( + reinterpret_cast<HANDLE>(0x123), + reinterpret_cast<HANDLE>(0x456), + reinterpret_cast<HANDLE>(0x789), + reinterpret_cast<HANDLE>(0xabc), + reinterpret_cast<HANDLE>(0xdef), + 0x7fff000012345678ull, + 0x100000ull, + 0xccccddddeeeeffffull); + EXPECT_TRUE(icd2.IsValid()); +} + +TEST(InitialClientData, RoundTrip) { + InitialClientData first( + reinterpret_cast<HANDLE>(0x123), + reinterpret_cast<HANDLE>(0x456), + reinterpret_cast<HANDLE>(0x789), + reinterpret_cast<HANDLE>(0xabc), + reinterpret_cast<HANDLE>(0xdef), + 0x7fff000012345678ull, + 0x100000ull, + 0xccccddddeeeeffffull); + + std::string as_string = first.StringRepresentation(); + EXPECT_EQ(as_string, + "0x123,0x456,0x789,0xabc,0xdef," + "0x7fff000012345678,0x100000,0xccccddddeeeeffff"); + + InitialClientData second; + ASSERT_TRUE(second.InitializeFromString(as_string)); + EXPECT_EQ(second.request_crash_dump(), first.request_crash_dump()); + EXPECT_EQ(second.request_non_crash_dump(), first.request_non_crash_dump()); + EXPECT_EQ(second.non_crash_dump_completed(), + first.non_crash_dump_completed()); + EXPECT_EQ(second.first_pipe_instance(), first.first_pipe_instance()); + EXPECT_EQ(second.client_process(), first.client_process()); + EXPECT_EQ(second.crash_exception_information(), + first.crash_exception_information()); + EXPECT_EQ(second.non_crash_exception_information(), + first.non_crash_exception_information()); + EXPECT_EQ(second.debug_critical_section_address(), + first.debug_critical_section_address()); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/loader_lock.cc b/src/third_party/crashpad/util/win/loader_lock.cc new file mode 100644 index 0000000..187ef84 --- /dev/null +++ b/src/third_party/crashpad/util/win/loader_lock.cc
@@ -0,0 +1,52 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/loader_lock.h" + +#include <windows.h> + +#include "build/build_config.h" +#include "util/win/process_structs.h" + +namespace crashpad { + +namespace { + +#ifdef ARCH_CPU_64_BITS +using NativeTraits = process_types::internal::Traits64; +#else +using NativeTraits = process_types::internal::Traits32; +#endif // ARCH_CPU_64_BITS + +using PEB = process_types::PEB<NativeTraits>; +using TEB = process_types::TEB<NativeTraits>; +using RTL_CRITICAL_SECTION = process_types::RTL_CRITICAL_SECTION<NativeTraits>; + +TEB* GetTeb() { + return reinterpret_cast<TEB*>(NtCurrentTeb()); +} + +PEB* GetPeb() { + return reinterpret_cast<PEB*>(GetTeb()->ProcessEnvironmentBlock); +} + +} // namespace + +bool IsThreadInLoaderLock() { + RTL_CRITICAL_SECTION* loader_lock = + reinterpret_cast<RTL_CRITICAL_SECTION*>(GetPeb()->LoaderLock); + return loader_lock->OwningThread == GetTeb()->ClientId.UniqueThread; +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/loader_lock.h b/src/third_party/crashpad/util/win/loader_lock.h new file mode 100644 index 0000000..6c6b311 --- /dev/null +++ b/src/third_party/crashpad/util/win/loader_lock.h
@@ -0,0 +1,25 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_LOADER_LOCK_H_ +#define CRASHPAD_UTIL_WIN_LOADER_LOCK_H_ + +namespace crashpad { + +//! \return `true` if the current thread holds the loader lock. +bool IsThreadInLoaderLock(); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_LOADER_LOCK_H_
diff --git a/src/third_party/crashpad/util/win/loader_lock_test.cc b/src/third_party/crashpad/util/win/loader_lock_test.cc new file mode 100644 index 0000000..d33ea5a --- /dev/null +++ b/src/third_party/crashpad/util/win/loader_lock_test.cc
@@ -0,0 +1,36 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/loader_lock.h" + +#include "gtest/gtest.h" +#include "util/win/get_function.h" + +extern "C" bool LoaderLockDetected(); + +namespace crashpad { +namespace test { +namespace { + +TEST(LoaderLock, Detected) { + EXPECT_FALSE(IsThreadInLoaderLock()); + auto* loader_lock_detected = GET_FUNCTION_REQUIRED( + L"crashpad_util_test_loader_lock_test.dll", LoaderLockDetected); + EXPECT_TRUE(loader_lock_detected()); + EXPECT_FALSE(IsThreadInLoaderLock()); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/loader_lock_test_dll.cc b/src/third_party/crashpad/util/win/loader_lock_test_dll.cc new file mode 100644 index 0000000..b673ab3 --- /dev/null +++ b/src/third_party/crashpad/util/win/loader_lock_test_dll.cc
@@ -0,0 +1,41 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <windows.h> + +#include "util/win/loader_lock.h" + +namespace { + +bool g_loader_lock_detected = false; + +} // namespace + +extern "C" { + +__declspec(dllexport) bool LoaderLockDetected() { + return g_loader_lock_detected; +} + +} // extern "C" + +BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) { + switch (reason) { + case DLL_PROCESS_ATTACH: + g_loader_lock_detected = crashpad::IsThreadInLoaderLock(); + break; + } + + return TRUE; +}
diff --git a/src/third_party/crashpad/util/win/module_version.cc b/src/third_party/crashpad/util/win/module_version.cc new file mode 100644 index 0000000..bd83538 --- /dev/null +++ b/src/third_party/crashpad/util/win/module_version.cc
@@ -0,0 +1,58 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/module_version.h" + +#include <windows.h> +#include <stdint.h> + +#include <memory> + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace crashpad { + +bool GetModuleVersionAndType(const base::FilePath& path, + VS_FIXEDFILEINFO* vs_fixedfileinfo) { + DWORD size = GetFileVersionInfoSize(path.value().c_str(), nullptr); + if (!size) { + PLOG_IF(WARNING, GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND) + << "GetFileVersionInfoSize: " << base::UTF16ToUTF8(path.value()); + return false; + } + + std::unique_ptr<uint8_t[]> data(new uint8_t[size]); + if (!GetFileVersionInfo(path.value().c_str(), 0, size, data.get())) { + PLOG(WARNING) << "GetFileVersionInfo: " + << base::UTF16ToUTF8(path.value()); + return false; + } + + VS_FIXEDFILEINFO* fixed_file_info; + UINT ffi_size; + if (!VerQueryValue(data.get(), + L"\\", + reinterpret_cast<void**>(&fixed_file_info), + &ffi_size)) { + PLOG(WARNING) << "VerQueryValue"; + return false; + } + + *vs_fixedfileinfo = *fixed_file_info; + vs_fixedfileinfo->dwFileFlags &= vs_fixedfileinfo->dwFileFlagsMask; + return true; +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/module_version.h b/src/third_party/crashpad/util/win/module_version.h new file mode 100644 index 0000000..afc2a50 --- /dev/null +++ b/src/third_party/crashpad/util/win/module_version.h
@@ -0,0 +1,45 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_MODULE_VERSION_H_ +#define CRASHPAD_UTIL_WIN_MODULE_VERSION_H_ + +#include <windows.h> + +#include "base/files/file_path.h" + +namespace crashpad { + +//! \brief Retrieve the type and version information from a given module (exe, +//! dll, etc.) +//! +//! This function calls `GetFileVersionInfo()`, which can implicitly call +//! `LoadLibrary()` to load \a path into the calling process. Do not call this +//! function on an untrusted module, because there is a risk of executing the +//! module’s code. +//! +//! \param[in] path The path to the module to be inspected. +//! \param[out] vs_fixedfileinfo The VS_FIXEDFILEINFO on success. +//! VS_FIXEDFILEINFO::dwFileFlags will have been masked with +//! VS_FIXEDFILEINFO::dwFileFlagsMask already. +//! +//! \return `true` on success, or `false` on failure with a message logged. If +//! the module has no `VERSIONINFO` resource, `false` will be returned +//! without any messages logged. +bool GetModuleVersionAndType(const base::FilePath& path, + VS_FIXEDFILEINFO* vs_fixedfileinfo); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_MODULE_VERSION_H_
diff --git a/src/third_party/crashpad/util/win/nt_internals.cc b/src/third_party/crashpad/util/win/nt_internals.cc new file mode 100644 index 0000000..df8288a --- /dev/null +++ b/src/third_party/crashpad/util/win/nt_internals.cc
@@ -0,0 +1,176 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/nt_internals.h" + +#include "base/logging.h" +#include "util/win/get_function.h" + +// Declarations that the system headers should provide but don’t. + +extern "C" { + +NTSTATUS NTAPI NtCreateThreadEx(PHANDLE ThreadHandle, + ACCESS_MASK DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + HANDLE ProcessHandle, + PVOID StartRoutine, + PVOID Argument, + ULONG CreateFlags, + SIZE_T ZeroBits, + SIZE_T StackSize, + SIZE_T MaximumStackSize, + PVOID /*PPS_ATTRIBUTE_LIST*/ AttributeList); + +NTSTATUS NTAPI NtOpenThread(HANDLE* ThreadHandle, + ACCESS_MASK DesiredAccess, + OBJECT_ATTRIBUTES* ObjectAttributes, + CLIENT_ID* ClientId); + +NTSTATUS NTAPI NtSuspendProcess(HANDLE); + +NTSTATUS NTAPI NtResumeProcess(HANDLE); + +VOID NTAPI RtlGetUnloadEventTraceEx(PULONG* ElementSize, + PULONG* ElementCount, + PVOID* EventTrace); + +} // extern "C" + +namespace crashpad { + +NTSTATUS NtClose(HANDLE handle) { + static const auto nt_close = GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtClose); + return nt_close(handle); +} + +NTSTATUS +NtCreateThreadEx(PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + HANDLE process_handle, + PVOID start_routine, + PVOID argument, + ULONG create_flags, + SIZE_T zero_bits, + SIZE_T stack_size, + SIZE_T maximum_stack_size, + PVOID attribute_list) { + static const auto nt_create_thread_ex = + GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtCreateThreadEx); + return nt_create_thread_ex(thread_handle, + desired_access, + object_attributes, + process_handle, + start_routine, + argument, + create_flags, + zero_bits, + stack_size, + maximum_stack_size, + attribute_list); +} + +NTSTATUS NtQuerySystemInformation( + SYSTEM_INFORMATION_CLASS system_information_class, + PVOID system_information, + ULONG system_information_length, + PULONG return_length) { + static const auto nt_query_system_information = + GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQuerySystemInformation); + return nt_query_system_information(system_information_class, + system_information, + system_information_length, + return_length); +} + +NTSTATUS NtQueryInformationThread(HANDLE thread_handle, + THREADINFOCLASS thread_information_class, + PVOID thread_information, + ULONG thread_information_length, + PULONG return_length) { + static const auto nt_query_information_thread = + GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryInformationThread); + return nt_query_information_thread(thread_handle, + thread_information_class, + thread_information, + thread_information_length, + return_length); +} + +template <class Traits> +NTSTATUS NtOpenThread(PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + const process_types::CLIENT_ID<Traits>* client_id) { + static const auto nt_open_thread = + GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtOpenThread); + return nt_open_thread( + thread_handle, + desired_access, + object_attributes, + const_cast<CLIENT_ID*>(reinterpret_cast<const CLIENT_ID*>(client_id))); +} + +NTSTATUS NtQueryObject(HANDLE handle, + OBJECT_INFORMATION_CLASS object_information_class, + void* object_information, + ULONG object_information_length, + ULONG* return_length) { + static const auto nt_query_object = + GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryObject); + return nt_query_object(handle, + object_information_class, + object_information, + object_information_length, + return_length); +} + +NTSTATUS NtSuspendProcess(HANDLE handle) { + static const auto nt_suspend_process = + GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtSuspendProcess); + return nt_suspend_process(handle); +} + +NTSTATUS NtResumeProcess(HANDLE handle) { + static const auto nt_resume_process = + GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtResumeProcess); + return nt_resume_process(handle); +} + +void RtlGetUnloadEventTraceEx(ULONG** element_size, + ULONG** element_count, + void** event_trace) { + static const auto rtl_get_unload_event_trace_ex = + GET_FUNCTION_REQUIRED(L"ntdll.dll", ::RtlGetUnloadEventTraceEx); + rtl_get_unload_event_trace_ex(element_size, element_count, event_trace); +} + +// Explicit instantiations with the only 2 valid template arguments to avoid +// putting the body of the function in the header. +template NTSTATUS NtOpenThread<process_types::internal::Traits32>( + PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + const process_types::CLIENT_ID<process_types::internal::Traits32>* + client_id); + +template NTSTATUS NtOpenThread<process_types::internal::Traits64>( + PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + const process_types::CLIENT_ID<process_types::internal::Traits64>* + client_id); + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/nt_internals.h b/src/third_party/crashpad/util/win/nt_internals.h new file mode 100644 index 0000000..dad0405 --- /dev/null +++ b/src/third_party/crashpad/util/win/nt_internals.h
@@ -0,0 +1,99 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_NT_INTERNALS_H_ +#define CRASHPAD_UTIL_WIN_NT_INTERNALS_H_ + +#include <windows.h> +#include <winternl.h> + +#include "util/win/process_structs.h" + +// Copied from ntstatus.h because um/winnt.h conflicts with general inclusion of +// ntstatus.h. +#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L) +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#define STATUS_PROCESS_IS_TERMINATING ((NTSTATUS)0xC000010AL) + +namespace crashpad { + +NTSTATUS NtClose(HANDLE handle); + +// http://processhacker.sourceforge.net/doc/ntpsapi_8h_source.html +#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002 +NTSTATUS +NtCreateThreadEx(PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + HANDLE process_handle, + PVOID start_routine, + PVOID argument, + ULONG create_flags, + SIZE_T zero_bits, + SIZE_T stack_size, + SIZE_T maximum_stack_size, + PVOID /*PPS_ATTRIBUTE_LIST*/ attribute_list); + +// winternal.h defines THREADINFOCLASS, but not all members. +enum { ThreadBasicInformation = 0 }; + +// winternal.h defines SYSTEM_INFORMATION_CLASS, but not all members. +enum { SystemExtendedHandleInformation = 64 }; + +NTSTATUS NtQuerySystemInformation( + SYSTEM_INFORMATION_CLASS system_information_class, + PVOID system_information, + ULONG system_information_length, + PULONG return_length); + +NTSTATUS NtQueryInformationThread(HANDLE thread_handle, + THREADINFOCLASS thread_information_class, + PVOID thread_information, + ULONG thread_information_length, + PULONG return_length); + +template <class Traits> +NTSTATUS NtOpenThread(PHANDLE thread_handle, + ACCESS_MASK desired_access, + POBJECT_ATTRIBUTES object_attributes, + const process_types::CLIENT_ID<Traits>* client_id); + +NTSTATUS NtQueryObject(HANDLE handle, + OBJECT_INFORMATION_CLASS object_information_class, + void* object_information, + ULONG object_information_length, + ULONG* return_length); + +NTSTATUS NtSuspendProcess(HANDLE handle); + +NTSTATUS NtResumeProcess(HANDLE handle); + +// From https://msdn.microsoft.com/library/cc678403.aspx. +template <class Traits> +struct RTL_UNLOAD_EVENT_TRACE { + typename Traits::Pointer BaseAddress; + typename Traits::UnsignedIntegral SizeOfImage; + ULONG Sequence; + ULONG TimeDateStamp; + ULONG CheckSum; + WCHAR ImageName[32]; +}; + +void RtlGetUnloadEventTraceEx(ULONG** element_size, + ULONG** element_count, + void** event_trace); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_NT_INTERNALS_H_
diff --git a/src/third_party/crashpad/util/win/ntstatus_logging.cc b/src/third_party/crashpad/util/win/ntstatus_logging.cc new file mode 100644 index 0000000..e9a9b61 --- /dev/null +++ b/src/third_party/crashpad/util/win/ntstatus_logging.cc
@@ -0,0 +1,75 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/ntstatus_logging.h" + +#include <string> + +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" + +namespace { + +std::string FormatNtstatus(DWORD ntstatus) { + char msgbuf[256]; + DWORD len = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_FROM_HMODULE, + GetModuleHandle(L"ntdll.dll"), + ntstatus, + 0, + msgbuf, + static_cast<DWORD>(base::size(msgbuf)), + nullptr); + if (len) { + // Most system messages end in a period and a space. Remove the space if + // it’s there, because ~NtstatusLogMessage() includes one. + if (len >= 1 && msgbuf[len - 1] == ' ') { + msgbuf[len - 1] = '\0'; + } + return msgbuf; + } else { + return base::StringPrintf("<failed to retrieve error message (0x%lx)>", + GetLastError()); + } +} + +} // namespace + +namespace logging { + +NtstatusLogMessage::NtstatusLogMessage( +#if defined(MINI_CHROMIUM_BASE_LOGGING_H_) + const char* function, +#endif + const char* file_path, + int line, + LogSeverity severity, + DWORD ntstatus) + : LogMessage( +#if defined(MINI_CHROMIUM_BASE_LOGGING_H_) + function, +#endif + file_path, + line, + severity), + ntstatus_(ntstatus) { +} + +NtstatusLogMessage::~NtstatusLogMessage() { + stream() << ": " << FormatNtstatus(ntstatus_) + << base::StringPrintf(" (0x%08lx)", ntstatus_); +} + +} // namespace logging
diff --git a/src/third_party/crashpad/util/win/ntstatus_logging.h b/src/third_party/crashpad/util/win/ntstatus_logging.h new file mode 100644 index 0000000..7eececc --- /dev/null +++ b/src/third_party/crashpad/util/win/ntstatus_logging.h
@@ -0,0 +1,98 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_NTSTATUS_LOGGING_H_ +#define CRASHPAD_UTIL_WIN_NTSTATUS_LOGGING_H_ + +#include <windows.h> + +#include "base/logging.h" +#include "base/macros.h" + +namespace logging { + +class NtstatusLogMessage : public logging::LogMessage { + public: + NtstatusLogMessage( +#if defined(MINI_CHROMIUM_BASE_LOGGING_H_) + const char* function, +#endif + const char* file_path, + int line, + LogSeverity severity, + DWORD ntstatus); + ~NtstatusLogMessage(); + + private: + DWORD ntstatus_; + + DISALLOW_COPY_AND_ASSIGN(NtstatusLogMessage); +}; + +} // namespace logging + +#define NTSTATUS_LOG_STREAM(severity, ntstatus) \ + COMPACT_GOOGLE_LOG_EX_##severity(NtstatusLogMessage, ntstatus).stream() + +#if defined(MINI_CHROMIUM_BASE_LOGGING_H_) + +#define NTSTATUS_VLOG_STREAM(verbose_level, ntstatus) \ + logging::NtstatusLogMessage( \ + __PRETTY_FUNCTION__, __FILE__, __LINE__, -verbose_level, ntstatus) \ + .stream() + +#else + +#define NTSTATUS_VLOG_STREAM(verbose_level, ntstatus) \ + logging::NtstatusLogMessage(__FILE__, __LINE__, -verbose_level, ntstatus) \ + .stream() + +#endif // MINI_CHROMIUM_BASE_LOGGING_H_ + +#define NTSTATUS_LOG(severity, ntstatus) \ + LAZY_STREAM(NTSTATUS_LOG_STREAM(severity, ntstatus), LOG_IS_ON(severity)) +#define NTSTATUS_LOG_IF(severity, condition, ntstatus) \ + LAZY_STREAM(NTSTATUS_LOG_STREAM(severity, ntstatus), \ + LOG_IS_ON(severity) && (condition)) + +#define NTSTATUS_VLOG(verbose_level, ntstatus) \ + LAZY_STREAM(NTSTATUS_VLOG_STREAM(verbose_level, ntstatus), \ + VLOG_IS_ON(verbose_level)) +#define NTSTATUS_VLOG_IF(verbose_level, condition, ntstatus) \ + LAZY_STREAM(NTSTATUS_VLOG_STREAM(verbose_level, ntstatus), \ + VLOG_IS_ON(verbose_level) && (condition)) + +#define NTSTATUS_CHECK(condition, ntstatus) \ + LAZY_STREAM(NTSTATUS_LOG_STREAM(FATAL, ntstatus), !(condition)) \ + << "Check failed: " #condition << ". " + +#define NTSTATUS_DLOG(severity, ntstatus) \ + LAZY_STREAM(NTSTATUS_LOG_STREAM(severity, ntstatus), DLOG_IS_ON(severity)) +#define NTSTATUS_DLOG_IF(severity, condition, ntstatus) \ + LAZY_STREAM(NTSTATUS_LOG_STREAM(severity, ntstatus), \ + DLOG_IS_ON(severity) && (condition)) + +#define NTSTATUS_DVLOG(verbose_level, ntstatus) \ + LAZY_STREAM(NTSTATUS_VLOG_STREAM(verbose_level, ntstatus), \ + DVLOG_IS_ON(verbose_level)) +#define NTSTATUS_DVLOG_IF(verbose_level, condition, ntstatus) \ + LAZY_STREAM(NTSTATUS_VLOG_STREAM(verbose_level, ntstatus), \ + DVLOG_IS_ON(verbose_level) && (condition)) + +#define NTSTATUS_DCHECK(condition, ntstatus) \ + LAZY_STREAM(NTSTATUS_LOG_STREAM(FATAL, ntstatus), \ + DCHECK_IS_ON && !(condition)) \ + << "Check failed: " #condition << ". " + +#endif // CRASHPAD_UTIL_WIN_NTSTATUS_LOGGING_H_
diff --git a/src/third_party/crashpad/util/win/process_info.cc b/src/third_party/crashpad/util/win/process_info.cc new file mode 100644 index 0000000..49a6f27 --- /dev/null +++ b/src/third_party/crashpad/util/win/process_info.cc
@@ -0,0 +1,718 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/process_info.h" + +#include <stddef.h> +#include <winternl.h> + +#include <algorithm> +#include <limits> +#include <memory> +#include <new> +#include <type_traits> + +#include "base/logging.h" +#include "base/memory/free_deleter.h" +#include "base/process/memory.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "util/misc/from_pointer_cast.h" +#include "util/numeric/safe_assignment.h" +#include "util/win/get_function.h" +#include "util/win/handle.h" +#include "util/win/nt_internals.h" +#include "util/win/ntstatus_logging.h" +#include "util/win/process_structs.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { + +namespace { + +using UniqueMallocPtr = std::unique_ptr<uint8_t[], base::FreeDeleter>; + +UniqueMallocPtr UncheckedAllocate(size_t size) { + void* raw_ptr = nullptr; + if (!base::UncheckedMalloc(size, &raw_ptr)) + return UniqueMallocPtr(); + + return UniqueMallocPtr(new (raw_ptr) uint8_t[size]); +} + +NTSTATUS NtQueryInformationProcess(HANDLE process_handle, + PROCESSINFOCLASS process_information_class, + PVOID process_information, + ULONG process_information_length, + PULONG return_length) { + static const auto nt_query_information_process = + GET_FUNCTION_REQUIRED(L"ntdll.dll", ::NtQueryInformationProcess); + return nt_query_information_process(process_handle, + process_information_class, + process_information, + process_information_length, + return_length); +} + +bool IsProcessWow64(HANDLE process_handle) { + static const auto is_wow64_process = + GET_FUNCTION(L"kernel32.dll", ::IsWow64Process); + if (!is_wow64_process) + return false; + BOOL is_wow64; + if (!is_wow64_process(process_handle, &is_wow64)) { + PLOG(ERROR) << "IsWow64Process"; + return false; + } + return !!is_wow64; +} + +template <class T> +bool ReadUnicodeString(HANDLE process, + const process_types::UNICODE_STRING<T>& us, + std::wstring* result) { + if (us.Length == 0) { + result->clear(); + return true; + } + DCHECK_EQ(us.Length % sizeof(wchar_t), 0u); + result->resize(us.Length / sizeof(wchar_t)); + SIZE_T bytes_read; + if (!ReadProcessMemory( + process, + reinterpret_cast<const void*>(static_cast<uintptr_t>(us.Buffer)), + &result->operator[](0), + us.Length, + &bytes_read)) { + PLOG(ERROR) << "ReadProcessMemory UNICODE_STRING"; + return false; + } + if (bytes_read != us.Length) { + LOG(ERROR) << "ReadProcessMemory UNICODE_STRING incorrect size"; + return false; + } + return true; +} + +template <class T> +bool ReadStruct(HANDLE process, WinVMAddress at, T* into) { + SIZE_T bytes_read; + if (!ReadProcessMemory(process, + reinterpret_cast<const void*>(at), + into, + sizeof(T), + &bytes_read)) { + // We don't have a name for the type we're reading, so include the signature + // to get the type of T. + PLOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__; + return false; + } + if (bytes_read != sizeof(T)) { + LOG(ERROR) << "ReadProcessMemory " << __FUNCSIG__ << " incorrect size"; + return false; + } + return true; +} + +bool RegionIsAccessible(const MEMORY_BASIC_INFORMATION64& memory_info) { + return memory_info.State == MEM_COMMIT && + (memory_info.Protect & PAGE_NOACCESS) == 0 && + (memory_info.Protect & PAGE_GUARD) == 0; +} + +MEMORY_BASIC_INFORMATION64 MemoryBasicInformationToMemoryBasicInformation64( + const MEMORY_BASIC_INFORMATION& mbi) { + MEMORY_BASIC_INFORMATION64 mbi64 = {0}; + mbi64.BaseAddress = FromPointerCast<ULONGLONG>(mbi.BaseAddress); + mbi64.AllocationBase = reinterpret_cast<ULONGLONG>(mbi.AllocationBase); + mbi64.AllocationProtect = mbi.AllocationProtect; + mbi64.RegionSize = mbi.RegionSize; + mbi64.State = mbi.State; + mbi64.Protect = mbi.Protect; + mbi64.Type = mbi.Type; + return mbi64; +} + +// NtQueryObject with a retry for size mismatch as well as a minimum size to +// retrieve (and expect). +std::unique_ptr<uint8_t[]> QueryObject( + HANDLE handle, + OBJECT_INFORMATION_CLASS object_information_class, + ULONG minimum_size) { + ULONG size = minimum_size; + ULONG return_length; + std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]); + NTSTATUS status = crashpad::NtQueryObject( + handle, object_information_class, buffer.get(), size, &return_length); + if (status == STATUS_INFO_LENGTH_MISMATCH) { + DCHECK_GT(return_length, size); + size = return_length; + + // Free the old buffer before attempting to allocate a new one. + buffer.reset(); + + buffer.reset(new uint8_t[size]); + status = crashpad::NtQueryObject( + handle, object_information_class, buffer.get(), size, &return_length); + } + + if (!NT_SUCCESS(status)) { + NTSTATUS_LOG(ERROR, status) << "NtQueryObject"; + return nullptr; + } + + DCHECK_LE(return_length, size); + DCHECK_GE(return_length, minimum_size); + return buffer; +} + +} // namespace + +template <class Traits> +bool GetProcessBasicInformation(HANDLE process, + bool is_wow64, + ProcessInfo* process_info, + WinVMAddress* peb_address, + WinVMSize* peb_size) { + ULONG bytes_returned; + process_types::PROCESS_BASIC_INFORMATION<Traits> process_basic_information; + NTSTATUS status = + crashpad::NtQueryInformationProcess(process, + ProcessBasicInformation, + &process_basic_information, + sizeof(process_basic_information), + &bytes_returned); + if (!NT_SUCCESS(status)) { + NTSTATUS_LOG(ERROR, status) << "NtQueryInformationProcess"; + return false; + } + if (bytes_returned != sizeof(process_basic_information)) { + LOG(ERROR) << "NtQueryInformationProcess incorrect size"; + return false; + } + + // API functions (e.g. OpenProcess) take only a DWORD, so there's no sense in + // maintaining the top bits. + process_info->process_id_ = + static_cast<DWORD>(process_basic_information.UniqueProcessId); + process_info->inherited_from_process_id_ = static_cast<DWORD>( + process_basic_information.InheritedFromUniqueProcessId); + + // We now want to read the PEB to gather the rest of our information. The + // PebBaseAddress as returned above is what we want for 64-on-64 and 32-on-32, + // but for Wow64, we want to read the 32 bit PEB (a Wow64 process has both). + // The address of this is found by a second call to NtQueryInformationProcess. + if (!is_wow64) { + *peb_address = process_basic_information.PebBaseAddress; + *peb_size = sizeof(process_types::PEB<Traits>); + } else { + ULONG_PTR wow64_peb_address; + status = crashpad::NtQueryInformationProcess(process, + ProcessWow64Information, + &wow64_peb_address, + sizeof(wow64_peb_address), + &bytes_returned); + if (!NT_SUCCESS(status)) { + NTSTATUS_LOG(ERROR, status) << "NtQueryInformationProcess"; + return false; + } + if (bytes_returned != sizeof(wow64_peb_address)) { + LOG(ERROR) << "NtQueryInformationProcess incorrect size"; + return false; + } + *peb_address = wow64_peb_address; + *peb_size = sizeof(process_types::PEB<process_types::internal::Traits32>); + } + + return true; +} + +template <class Traits> +bool ReadProcessData(HANDLE process, + WinVMAddress peb_address_vmaddr, + ProcessInfo* process_info) { + typename Traits::Pointer peb_address; + if (!AssignIfInRange(&peb_address, peb_address_vmaddr)) { + LOG(ERROR) << base::StringPrintf("peb address 0x%llx out of range", + peb_address_vmaddr); + return false; + } + + // Try to read the process environment block. + process_types::PEB<Traits> peb; + if (!ReadStruct(process, peb_address, &peb)) + return false; + + process_types::RTL_USER_PROCESS_PARAMETERS<Traits> process_parameters; + if (!ReadStruct(process, peb.ProcessParameters, &process_parameters)) + return false; + + if (!ReadUnicodeString(process, + process_parameters.CommandLine, + &process_info->command_line_)) { + return false; + } + + process_types::PEB_LDR_DATA<Traits> peb_ldr_data; + if (!ReadStruct(process, peb.Ldr, &peb_ldr_data)) + return false; + + process_types::LDR_DATA_TABLE_ENTRY<Traits> ldr_data_table_entry; + ProcessInfo::Module module; + + // Walk the PEB LDR structure (doubly-linked list) to get the list of loaded + // modules. We use this method rather than EnumProcessModules to get the + // modules in load order rather than memory order. Notably, this includes the + // main executable as the first element. + typename Traits::Pointer last = peb_ldr_data.InLoadOrderModuleList.Blink; + for (typename Traits::Pointer cur = peb_ldr_data.InLoadOrderModuleList.Flink;; + cur = ldr_data_table_entry.InLoadOrderLinks.Flink) { + // |cur| is the pointer to the LIST_ENTRY embedded in the + // LDR_DATA_TABLE_ENTRY, in the target process's address space. So we need + // to read from the target, and also offset back to the beginning of the + // structure. + if (!ReadStruct(process, + static_cast<WinVMAddress>(cur) - + offsetof(process_types::LDR_DATA_TABLE_ENTRY<Traits>, + InLoadOrderLinks), + &ldr_data_table_entry)) { + break; + } + // TODO(scottmg): Capture Checksum, etc. too? + if (!ReadUnicodeString( + process, ldr_data_table_entry.FullDllName, &module.name)) { + module.name = L"???"; + } + module.dll_base = ldr_data_table_entry.DllBase; + module.size = ldr_data_table_entry.SizeOfImage; + module.timestamp = ldr_data_table_entry.TimeDateStamp; + process_info->modules_.push_back(module); + if (cur == last) + break; + } + + return true; +} + +bool ReadMemoryInfo(HANDLE process, bool is_64_bit, ProcessInfo* process_info) { + DCHECK(process_info->memory_info_.empty()); + + constexpr WinVMAddress min_address = 0; + // We can't use GetSystemInfo() to get the address space range for another + // process. VirtualQueryEx() will fail with ERROR_INVALID_PARAMETER if the + // address is above the highest memory address accessible to the process, so + // we just probe the entire potential range (2^32 for x86, or 2^64 for x64). + const WinVMAddress max_address = is_64_bit + ? std::numeric_limits<uint64_t>::max() + : std::numeric_limits<uint32_t>::max(); + MEMORY_BASIC_INFORMATION memory_basic_information; + for (WinVMAddress address = min_address; address <= max_address; + address += memory_basic_information.RegionSize) { + size_t result = VirtualQueryEx(process, + reinterpret_cast<void*>(address), + &memory_basic_information, + sizeof(memory_basic_information)); + if (result == 0) { + if (GetLastError() == ERROR_INVALID_PARAMETER) + break; + PLOG(ERROR) << "VirtualQueryEx"; + return false; + } + + process_info->memory_info_.push_back( + MemoryBasicInformationToMemoryBasicInformation64( + memory_basic_information)); + + if (memory_basic_information.RegionSize == 0) { + LOG(ERROR) << "RegionSize == 0"; + return false; + } + } + + return true; +} + +std::vector<ProcessInfo::Handle> ProcessInfo::BuildHandleVector( + HANDLE process) const { + ULONG buffer_size = 2 * 1024 * 1024; + // Typically if the buffer were too small, STATUS_INFO_LENGTH_MISMATCH would + // return the correct size in the final argument, but it does not for + // SystemExtendedHandleInformation, so we loop and attempt larger sizes. + NTSTATUS status; + ULONG returned_length; + UniqueMallocPtr buffer; + for (int tries = 0; tries < 5; ++tries) { + buffer.reset(); + buffer = UncheckedAllocate(buffer_size); + if (!buffer) { + LOG(ERROR) << "UncheckedAllocate"; + return std::vector<Handle>(); + } + + status = crashpad::NtQuerySystemInformation( + static_cast<SYSTEM_INFORMATION_CLASS>(SystemExtendedHandleInformation), + buffer.get(), + buffer_size, + &returned_length); + if (NT_SUCCESS(status) || status != STATUS_INFO_LENGTH_MISMATCH) + break; + + buffer_size *= 2; + } + + if (!NT_SUCCESS(status)) { + NTSTATUS_LOG(ERROR, status) + << "NtQuerySystemInformation SystemExtendedHandleInformation"; + return std::vector<Handle>(); + } + + const auto& system_handle_information_ex = + *reinterpret_cast<process_types::SYSTEM_HANDLE_INFORMATION_EX*>( + buffer.get()); + + DCHECK_LE(offsetof(process_types::SYSTEM_HANDLE_INFORMATION_EX, Handles) + + system_handle_information_ex.NumberOfHandles * + sizeof(system_handle_information_ex.Handles[0]), + returned_length); + + std::vector<Handle> handles; + + for (size_t i = 0; i < system_handle_information_ex.NumberOfHandles; ++i) { + const auto& handle = system_handle_information_ex.Handles[i]; + if (handle.UniqueProcessId != process_id_) + continue; + + Handle result_handle; + result_handle.handle = HandleToInt(handle.HandleValue); + result_handle.attributes = handle.HandleAttributes; + result_handle.granted_access = handle.GrantedAccess; + + // TODO(scottmg): Could special case for self. + HANDLE dup_handle; + if (DuplicateHandle(process, + handle.HandleValue, + GetCurrentProcess(), + &dup_handle, + 0, + false, + DUPLICATE_SAME_ACCESS)) { + // Some handles cannot be duplicated, for example, handles of type + // EtwRegistration. If we fail to duplicate, then we can't gather any more + // information, but include the information that we do have already. + ScopedKernelHANDLE scoped_dup_handle(dup_handle); + + std::unique_ptr<uint8_t[]> object_basic_information_buffer = + QueryObject(dup_handle, + ObjectBasicInformation, + sizeof(PUBLIC_OBJECT_BASIC_INFORMATION)); + if (object_basic_information_buffer) { + PUBLIC_OBJECT_BASIC_INFORMATION* object_basic_information = + reinterpret_cast<PUBLIC_OBJECT_BASIC_INFORMATION*>( + object_basic_information_buffer.get()); + // The Attributes and GrantedAccess sometimes differ slightly between + // the data retrieved in SYSTEM_HANDLE_INFORMATION_EX and + // PUBLIC_OBJECT_TYPE_INFORMATION. We prefer the values in + // SYSTEM_HANDLE_INFORMATION_EX because they were retrieved from the + // target process, rather than on the duplicated handle, so don't use + // them here. + + // Subtract one to account for our DuplicateHandle() and another for + // NtQueryObject() while the query was being executed. + DCHECK_GT(object_basic_information->PointerCount, 2u); + result_handle.pointer_count = + object_basic_information->PointerCount - 2; + + // Subtract one to account for our DuplicateHandle(). + DCHECK_GT(object_basic_information->HandleCount, 1u); + result_handle.handle_count = object_basic_information->HandleCount - 1; + } + + std::unique_ptr<uint8_t[]> object_type_information_buffer = + QueryObject(dup_handle, + ObjectTypeInformation, + sizeof(PUBLIC_OBJECT_TYPE_INFORMATION)); + if (object_type_information_buffer) { + PUBLIC_OBJECT_TYPE_INFORMATION* object_type_information = + reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>( + object_type_information_buffer.get()); + + DCHECK_EQ(object_type_information->TypeName.Length % + sizeof(result_handle.type_name[0]), + 0u); + result_handle.type_name = + std::wstring(object_type_information->TypeName.Buffer, + object_type_information->TypeName.Length / + sizeof(result_handle.type_name[0])); + } + } + + handles.push_back(result_handle); + } + return handles; +} + +ProcessInfo::Module::Module() : name(), dll_base(0), size(0), timestamp() { +} + +ProcessInfo::Module::~Module() { +} + +ProcessInfo::Handle::Handle() + : type_name(), + handle(0), + attributes(0), + granted_access(0), + pointer_count(0), + handle_count(0) { +} + +ProcessInfo::Handle::~Handle() { +} + +ProcessInfo::ProcessInfo() + : process_id_(), + inherited_from_process_id_(), + process_(), + command_line_(), + peb_address_(0), + peb_size_(0), + modules_(), + memory_info_(), + handles_(), + is_64_bit_(false), + is_wow64_(false), + initialized_() { +} + +ProcessInfo::~ProcessInfo() { +} + +bool ProcessInfo::Initialize(HANDLE process) { + INITIALIZATION_STATE_SET_INITIALIZING(initialized_); + + process_ = process; + + is_wow64_ = IsProcessWow64(process); + + if (is_wow64_) { + // If it's WoW64, then it's 32-on-64. + is_64_bit_ = false; + } else { + // Otherwise, it's either 32 on 32, or 64 on 64. Use GetSystemInfo() to + // distinguish between these two cases. + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + +#if defined(ARCH_CPU_X86_FAMILY) + constexpr uint16_t kNative64BitArchitecture = PROCESSOR_ARCHITECTURE_AMD64; +#elif defined(ARCH_CPU_ARM_FAMILY) + constexpr uint16_t kNative64BitArchitecture = PROCESSOR_ARCHITECTURE_ARM64; +#endif + + is_64_bit_ = system_info.wProcessorArchitecture == kNative64BitArchitecture; + } + +#if defined(ARCH_CPU_32_BITS) + if (is_64_bit_) { + LOG(ERROR) << "Reading x64 process from x86 process not supported"; + return false; + } +#endif // ARCH_CPU_32_BITS + +#if defined(ARCH_CPU_64_BITS) + bool result = GetProcessBasicInformation<process_types::internal::Traits64>( + process, is_wow64_, this, &peb_address_, &peb_size_); +#else + bool result = GetProcessBasicInformation<process_types::internal::Traits32>( + process, false, this, &peb_address_, &peb_size_); +#endif // ARCH_CPU_64_BITS + + if (!result) { + LOG(ERROR) << "GetProcessBasicInformation failed"; + return false; + } + + result = is_64_bit_ ? ReadProcessData<process_types::internal::Traits64>( + process, peb_address_, this) + : ReadProcessData<process_types::internal::Traits32>( + process, peb_address_, this); + if (!result) { + LOG(ERROR) << "ReadProcessData failed"; + return false; + } + + if (!ReadMemoryInfo(process, is_64_bit_, this)) { + LOG(ERROR) << "ReadMemoryInfo failed"; + return false; + } + + INITIALIZATION_STATE_SET_VALID(initialized_); + return true; +} + +bool ProcessInfo::Is64Bit() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return is_64_bit_; +} + +bool ProcessInfo::IsWow64() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return is_wow64_; +} + +crashpad::ProcessID ProcessInfo::ProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return process_id_; +} + +crashpad::ProcessID ProcessInfo::ParentProcessID() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return inherited_from_process_id_; +} + +bool ProcessInfo::CommandLine(std::wstring* command_line) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *command_line = command_line_; + return true; +} + +void ProcessInfo::Peb(WinVMAddress* peb_address, WinVMSize* peb_size) const { + *peb_address = peb_address_; + *peb_size = peb_size_; +} + +bool ProcessInfo::Modules(std::vector<Module>* modules) const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + *modules = modules_; + return true; +} + +const ProcessInfo::MemoryBasicInformation64Vector& ProcessInfo::MemoryInfo() + const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + return memory_info_; +} + +std::vector<CheckedRange<WinVMAddress, WinVMSize>> +ProcessInfo::GetReadableRanges( + const CheckedRange<WinVMAddress, WinVMSize>& range) const { + return GetReadableRangesOfMemoryMap(range, MemoryInfo()); +} + +bool ProcessInfo::LoggingRangeIsFullyReadable( + const CheckedRange<WinVMAddress, WinVMSize>& range) const { + const auto ranges = GetReadableRanges(range); + if (ranges.empty()) { + LOG(ERROR) << base::StringPrintf( + "range at 0x%llx, size 0x%llx fully unreadable", + range.base(), + range.size()); + return false; + } + + if (ranges.size() != 1 || + ranges[0].base() != range.base() || ranges[0].size() != range.size()) { + LOG(ERROR) << base::StringPrintf( + "range at 0x%llx, size 0x%llx partially unreadable", + range.base(), + range.size()); + return false; + } + + return true; +} + +const std::vector<ProcessInfo::Handle>& ProcessInfo::Handles() const { + INITIALIZATION_STATE_DCHECK_VALID(initialized_); + if (handles_.empty()) + handles_ = BuildHandleVector(process_); + return handles_; +} + +std::vector<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRangesOfMemoryMap( + const CheckedRange<WinVMAddress, WinVMSize>& range, + const ProcessInfo::MemoryBasicInformation64Vector& memory_info) { + using Range = CheckedRange<WinVMAddress, WinVMSize>; + + // Constructing Ranges and using OverlapsRange() is very, very slow in Debug + // builds, so do a manual check in this loop. The ranges are still validated + // by a CheckedRange before being returned. + WinVMAddress range_base = range.base(); + WinVMAddress range_end = range.end(); + + // Find all the ranges that overlap the target range, maintaining their order. + ProcessInfo::MemoryBasicInformation64Vector overlapping; + const size_t size = memory_info.size(); + + // This loop is written in an ugly fashion to make Debug performance + // reasonable. + const MEMORY_BASIC_INFORMATION64* begin = &memory_info[0]; + for (size_t i = 0; i < size; ++i) { + const MEMORY_BASIC_INFORMATION64& mi = *(begin + i); + static_assert(std::is_same<decltype(mi.BaseAddress), WinVMAddress>::value, + "expected range address to be WinVMAddress"); + static_assert(std::is_same<decltype(mi.RegionSize), WinVMSize>::value, + "expected range size to be WinVMSize"); + WinVMAddress mi_end = mi.BaseAddress + mi.RegionSize; + if (range_base < mi_end && mi.BaseAddress < range_end) + overlapping.push_back(mi); + } + if (overlapping.empty()) + return std::vector<Range>(); + + // For the first and last, trim to the boundary of the incoming range. + MEMORY_BASIC_INFORMATION64& front = overlapping.front(); + WinVMAddress original_front_base_address = front.BaseAddress; + front.BaseAddress = std::max(front.BaseAddress, range.base()); + front.RegionSize = + (original_front_base_address + front.RegionSize) - front.BaseAddress; + + MEMORY_BASIC_INFORMATION64& back = overlapping.back(); + WinVMAddress back_end = back.BaseAddress + back.RegionSize; + back.RegionSize = std::min(range.end(), back_end) - back.BaseAddress; + + // Discard all non-accessible. + overlapping.erase(std::remove_if(overlapping.begin(), + overlapping.end(), + [](const MEMORY_BASIC_INFORMATION64& mbi) { + return !RegionIsAccessible(mbi); + }), + overlapping.end()); + if (overlapping.empty()) + return std::vector<Range>(); + + // Convert to return type. + std::vector<Range> as_ranges; + for (const auto& mi : overlapping) { + as_ranges.push_back(Range(mi.BaseAddress, mi.RegionSize)); + DCHECK(as_ranges.back().IsValid()); + } + + // Coalesce remaining regions. + std::vector<Range> result; + result.push_back(as_ranges[0]); + for (size_t i = 1; i < as_ranges.size(); ++i) { + if (result.back().end() == as_ranges[i].base()) { + result.back().SetRange(result.back().base(), + result.back().size() + as_ranges[i].size()); + } else { + result.push_back(as_ranges[i]); + } + DCHECK(result.back().IsValid()); + } + + return result; +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/process_info.h b/src/third_party/crashpad/util/win/process_info.h new file mode 100644 index 0000000..afbe146 --- /dev/null +++ b/src/third_party/crashpad/util/win/process_info.h
@@ -0,0 +1,221 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_PROCESS_INFO_H_ +#define CRASHPAD_UTIL_WIN_PROCESS_INFO_H_ + +#include <windows.h> +#include <sys/types.h> + +#include <string> +#include <vector> + +#include "base/macros.h" +#include "util/misc/initialization_state_dcheck.h" +#include "util/numeric/checked_range.h" +#include "util/process/process_id.h" +#include "util/stdlib/aligned_allocator.h" +#include "util/win/address_types.h" + +namespace crashpad { + +//! \brief Gathers information about a process given its `HANDLE`. This consists +//! primarily of information stored in the Process Environment Block. +class ProcessInfo { + public: + //! \brief The return type of MemoryInfo(), for convenience. + using MemoryBasicInformation64Vector = + AlignedVector<MEMORY_BASIC_INFORMATION64>; + + //! \brief Contains information about a module loaded into a process. + struct Module { + Module(); + ~Module(); + + //! \brief The pathname used to load the module from disk. + std::wstring name; + + //! \brief The base address of the loaded DLL. + WinVMAddress dll_base; + + //! \brief The size of the module. + WinVMSize size; + + //! \brief The module's timestamp. + time_t timestamp; + }; + + struct Handle { + Handle(); + ~Handle(); + + //! \brief A string representation of the handle's type. + std::wstring type_name; + + //! \brief The handle's value. + int handle; + + //! \brief The attributes for the handle, e.g. `OBJ_INHERIT`, + //! `OBJ_CASE_INSENSITIVE`, etc. + uint32_t attributes; + + //! \brief The `ACCESS_MASK` for the handle in this process. + //! + //! See + //! https://blogs.msdn.microsoft.com/openspecification/2010/04/01/about-the-access_mask-structure/ + //! for more information. + uint32_t granted_access; + + //! \brief The number of kernel references to the object that this handle + //! refers to. + uint32_t pointer_count; + + //! \brief The number of open handles to the object that this handle refers + //! to. + uint32_t handle_count; + }; + + ProcessInfo(); + ~ProcessInfo(); + + //! \brief Initializes this object with information about the given + //! \a process. + //! + //! This method must be called successfully prior to calling any other + //! method in this class. This method may only be called once. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool Initialize(HANDLE process); + + //! \return `true` if the target process is a 64-bit process. + bool Is64Bit() const; + + //! \return `true` if the target process is running on the Win32-on-Win64 + //! subsystem. + bool IsWow64() const; + + //! \return The target process's process ID. + crashpad::ProcessID ProcessID() const; + + //! \return The target process's parent process ID. + crashpad::ProcessID ParentProcessID() const; + + //! \return The command line from the target process's Process Environment + //! Block. + bool CommandLine(std::wstring* command_line) const; + + //! \brief Gets the address and size of the process's Process Environment + //! Block. + //! + //! \param[out] peb_address The address of the Process Environment Block. + //! \param[out] peb_size The size of the Process Environment Block. + void Peb(WinVMAddress* peb_address, WinVMSize* peb_size) const; + + //! \brief Retrieves the modules loaded into the target process. + //! + //! The modules are enumerated in initialization order as detailed in the + //! Process Environment Block. The main executable will always be the + //! first element. + bool Modules(std::vector<Module>* modules) const; + + //! \brief Retrieves information about all pages mapped into the process. + const MemoryBasicInformation64Vector& MemoryInfo() const; + + //! \brief Given a range to be read from the target process, returns a vector + //! of ranges, representing the readable portions of the original range. + //! + //! \param[in] range The range being identified. + //! + //! \return A vector of ranges corresponding to the portion of \a range that + //! is readable based on the memory map. + std::vector<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRanges( + const CheckedRange<WinVMAddress, WinVMSize>& range) const; + + //! \brief Given a range in the target process, determines if the entire range + //! is readable. + //! + //! \param[in] range The range being inspected. + //! + //! \return `true` if the range is fully readable, otherwise `false` with a + //! message logged. + bool LoggingRangeIsFullyReadable( + const CheckedRange<WinVMAddress, WinVMSize>& range) const; + + //! \brief Retrieves information about open handles in the target process. + const std::vector<Handle>& Handles() const; + + private: + template <class Traits> + friend bool GetProcessBasicInformation(HANDLE process, + bool is_wow64, + ProcessInfo* process_info, + WinVMAddress* peb_address, + WinVMSize* peb_size); + template <class Traits> + friend bool ReadProcessData(HANDLE process, + WinVMAddress peb_address_vmaddr, + ProcessInfo* process_info); + + friend bool ReadMemoryInfo(HANDLE process, + bool is_64_bit, + ProcessInfo* process_info); + + // This function is best-effort under low memory conditions. + std::vector<Handle> BuildHandleVector(HANDLE process) const; + + crashpad::ProcessID process_id_; + crashpad::ProcessID inherited_from_process_id_; + HANDLE process_; + std::wstring command_line_; + WinVMAddress peb_address_; + WinVMSize peb_size_; + std::vector<Module> modules_; + + // memory_info_ is a MemoryBasicInformation64Vector instead of a + // std::vector<MEMORY_BASIC_INFORMATION64> because MEMORY_BASIC_INFORMATION64 + // is declared with __declspec(align(16)), but std::vector<> does not maintain + // this alignment on 32-bit x86. clang-cl (but not MSVC cl) takes advantage of + // the presumed alignment and emits SSE instructions that require aligned + // storage. clang-cl should relax (unfortunately), but in the mean time, this + // provides aligned storage. See https://crbug.com/564691 and + // https://llvm.org/PR25779. + // + // TODO(mark): Remove this workaround when https://llvm.org/PR25779 is fixed + // and the fix is present in the clang-cl that compiles this code. + MemoryBasicInformation64Vector memory_info_; + + // Handles() is logically const, but updates this member on first retrieval. + // See https://crashpad.chromium.org/bug/9. + mutable std::vector<Handle> handles_; + + bool is_64_bit_; + bool is_wow64_; + InitializationStateDcheck initialized_; + + DISALLOW_COPY_AND_ASSIGN(ProcessInfo); +}; + +//! \brief Given a memory map of a process, and a range to be read from the +//! target process, returns a vector of ranges, representing the readable +//! portions of the original range. +//! +//! This is a free function for testing, but prefer +//! ProcessInfo::GetReadableRanges(). +std::vector<CheckedRange<WinVMAddress, WinVMSize>> GetReadableRangesOfMemoryMap( + const CheckedRange<WinVMAddress, WinVMSize>& range, + const ProcessInfo::MemoryBasicInformation64Vector& memory_info); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_PROCESS_INFO_H_
diff --git a/src/third_party/crashpad/util/win/process_info_test.cc b/src/third_party/crashpad/util/win/process_info_test.cc new file mode 100644 index 0000000..a43358d --- /dev/null +++ b/src/third_party/crashpad/util/win/process_info_test.cc
@@ -0,0 +1,650 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/process_info.h" + +#include <dbghelp.h> +#include <intrin.h> +#include <wchar.h> + +#include <memory> + +#include "base/files/file_path.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "test/errors.h" +#include "test/scoped_temp_dir.h" +#include "test/test_paths.h" +#include "test/win/child_launcher.h" +#include "util/file/file_io.h" +#include "util/misc/from_pointer_cast.h" +#include "util/misc/random_string.h" +#include "util/misc/uuid.h" +#include "util/win/command_line.h" +#include "util/win/get_function.h" +#include "util/win/handle.h" +#include "util/win/scoped_handle.h" +#include "util/win/scoped_registry_key.h" + +namespace crashpad { +namespace test { +namespace { + +constexpr wchar_t kNtdllName[] = L"\\ntdll.dll"; + +#if !defined(ARCH_CPU_64_BITS) +bool IsProcessWow64(HANDLE process_handle) { + static const auto is_wow64_process = + GET_FUNCTION(L"kernel32.dll", ::IsWow64Process); + if (!is_wow64_process) + return false; + BOOL is_wow64; + if (!is_wow64_process(process_handle, &is_wow64)) { + PLOG(ERROR) << "IsWow64Process"; + return false; + } + return !!is_wow64; +} +#endif + +void VerifyAddressInInCodePage(const ProcessInfo& process_info, + WinVMAddress code_address) { + // Make sure the child code address is an code page address with the right + // information. + const ProcessInfo::MemoryBasicInformation64Vector& memory_info = + process_info.MemoryInfo(); + bool found_region = false; + for (const auto& mi : memory_info) { + if (mi.BaseAddress <= code_address && + mi.BaseAddress + mi.RegionSize > code_address) { + EXPECT_EQ(mi.State, static_cast<DWORD>(MEM_COMMIT)); + EXPECT_EQ(mi.Protect, static_cast<DWORD>(PAGE_EXECUTE_READ)); + EXPECT_EQ(mi.Type, static_cast<DWORD>(MEM_IMAGE)); + EXPECT_FALSE(found_region); + found_region = true; + } + } + EXPECT_TRUE(found_region); +} + +TEST(ProcessInfo, Self) { + ProcessInfo process_info; + ASSERT_TRUE(process_info.Initialize(GetCurrentProcess())); + EXPECT_EQ(process_info.ProcessID(), GetCurrentProcessId()); + EXPECT_GT(process_info.ParentProcessID(), 0u); + +#if defined(ARCH_CPU_64_BITS) + EXPECT_TRUE(process_info.Is64Bit()); + EXPECT_FALSE(process_info.IsWow64()); +#else + EXPECT_FALSE(process_info.Is64Bit()); + if (IsProcessWow64(GetCurrentProcess())) + EXPECT_TRUE(process_info.IsWow64()); + else + EXPECT_FALSE(process_info.IsWow64()); +#endif + + std::wstring command_line; + EXPECT_TRUE(process_info.CommandLine(&command_line)); + EXPECT_EQ(command_line, std::wstring(GetCommandLine())); + + std::vector<ProcessInfo::Module> modules; + EXPECT_TRUE(process_info.Modules(&modules)); + ASSERT_GE(modules.size(), 2u); + std::wstring self_name = + std::wstring(1, '\\') + + TestPaths::ExpectedExecutableBasename(L"crashpad_util_test").value(); + ASSERT_GE(modules[0].name.size(), self_name.size()); + EXPECT_EQ(modules[0].name.substr(modules[0].name.size() - self_name.size()), + self_name); + ASSERT_GE(modules[1].name.size(), wcslen(kNtdllName)); + EXPECT_EQ(modules[1].name.substr(modules[1].name.size() - wcslen(kNtdllName)), + kNtdllName); + + EXPECT_EQ(modules[0].dll_base, + reinterpret_cast<uintptr_t>(GetModuleHandle(nullptr))); + EXPECT_EQ(modules[1].dll_base, + reinterpret_cast<uintptr_t>(GetModuleHandle(L"ntdll.dll"))); + + EXPECT_GT(modules[0].size, 0u); + EXPECT_GT(modules[1].size, 0u); + + EXPECT_EQ(modules[0].timestamp, + GetTimestampForLoadedLibrary(GetModuleHandle(nullptr))); + // System modules are forced to particular stamps and the file header values + // don't match the on-disk times. Just make sure we got some data here. + EXPECT_GT(modules[1].timestamp, 0); + + // Find something we know is a code address and confirm expected memory + // information settings. + VerifyAddressInInCodePage(process_info, + FromPointerCast<WinVMAddress>(_ReturnAddress())); +} + +void TestOtherProcess(TestPaths::Architecture architecture) { + ProcessInfo process_info; + + UUID done_uuid; + done_uuid.InitializeWithNew(); + + ScopedKernelHANDLE done( + CreateEvent(nullptr, true, false, done_uuid.ToString16().c_str())); + ASSERT_TRUE(done.get()) << ErrorMessage("CreateEvent"); + + base::FilePath child_test_executable = + TestPaths::BuildArtifact(L"util", + L"process_info_test_child", + TestPaths::FileType::kExecutable, + architecture); + std::wstring args; + AppendCommandLineArgument(done_uuid.ToString16(), &args); + + ChildLauncher child(child_test_executable, args); + ASSERT_NO_FATAL_FAILURE(child.Start()); + + // The child sends us a code address we can look up in the memory map. + WinVMAddress code_address; + CheckedReadFileExactly( + child.stdout_read_handle(), &code_address, sizeof(code_address)); + + ASSERT_TRUE(process_info.Initialize(child.process_handle())); + + // Tell the test it's OK to shut down now that we've read our data. + EXPECT_TRUE(SetEvent(done.get())) << ErrorMessage("SetEvent"); + + EXPECT_EQ(child.WaitForExit(), 0u); + + std::vector<ProcessInfo::Module> modules; + EXPECT_TRUE(process_info.Modules(&modules)); + ASSERT_GE(modules.size(), 3u); + std::wstring child_name = L"\\crashpad_util_test_process_info_test_child.exe"; + ASSERT_GE(modules[0].name.size(), child_name.size()); + EXPECT_EQ(modules[0].name.substr(modules[0].name.size() - child_name.size()), + child_name); + ASSERT_GE(modules[1].name.size(), wcslen(kNtdllName)); + EXPECT_EQ(modules[1].name.substr(modules[1].name.size() - wcslen(kNtdllName)), + kNtdllName); + // lz32.dll is an uncommonly-used-but-always-available module that the test + // binary manually loads. + static constexpr wchar_t kLz32dllName[] = L"\\lz32.dll"; + auto& lz32 = modules[modules.size() - 2]; + ASSERT_GE(lz32.name.size(), wcslen(kLz32dllName)); + EXPECT_EQ(lz32.name.substr(lz32.name.size() - wcslen(kLz32dllName)), + kLz32dllName); + + // Note that the test code corrupts the PEB MemoryOrder list, whereas + // ProcessInfo::Modules() retrieves the module names via the PEB LoadOrder + // list. These are expected to point to the same strings, but theoretically + // could be separate. + auto& corrupted = modules.back(); + EXPECT_EQ(corrupted.name, L"???"); + + VerifyAddressInInCodePage(process_info, code_address); +} + +TEST(ProcessInfo, OtherProcess) { + TestOtherProcess(TestPaths::Architecture::kDefault); +} + +#if defined(ARCH_CPU_64_BITS) +TEST(ProcessInfo, OtherProcessWOW64) { + if (!TestPaths::Has32BitBuildArtifacts()) { + GTEST_SKIP(); + } + + TestOtherProcess(TestPaths::Architecture::k32Bit); +} +#endif // ARCH_CPU_64_BITS + +TEST(ProcessInfo, AccessibleRangesNone) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 0; + mbi.RegionSize = 10; + mbi.State = MEM_FREE; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(2, 4), + memory_info); + + EXPECT_TRUE(result.empty()); +} + +TEST(ProcessInfo, AccessibleRangesOneInside) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 0; + mbi.RegionSize = 10; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(2, 4), + memory_info); + + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(result[0].base(), 2u); + EXPECT_EQ(result[0].size(), 4u); +} + +TEST(ProcessInfo, AccessibleRangesOneTruncatedSize) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 0; + mbi.RegionSize = 10; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + mbi.BaseAddress = 10; + mbi.RegionSize = 20; + mbi.State = MEM_FREE; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10), + memory_info); + + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(result[0].base(), 5u); + EXPECT_EQ(result[0].size(), 5u); +} + +TEST(ProcessInfo, AccessibleRangesOneMovedStart) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 0; + mbi.RegionSize = 10; + mbi.State = MEM_FREE; + memory_info.push_back(mbi); + + mbi.BaseAddress = 10; + mbi.RegionSize = 20; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10), + memory_info); + + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(result[0].base(), 10u); + EXPECT_EQ(result[0].size(), 5u); +} + +TEST(ProcessInfo, ReserveIsInaccessible) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 0; + mbi.RegionSize = 10; + mbi.State = MEM_RESERVE; + memory_info.push_back(mbi); + + mbi.BaseAddress = 10; + mbi.RegionSize = 20; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10), + memory_info); + + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(result[0].base(), 10u); + EXPECT_EQ(result[0].size(), 5u); +} + +TEST(ProcessInfo, PageGuardIsInaccessible) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 0; + mbi.RegionSize = 10; + mbi.State = MEM_COMMIT; + mbi.Protect = PAGE_GUARD; + memory_info.push_back(mbi); + + mbi.BaseAddress = 10; + mbi.RegionSize = 20; + mbi.State = MEM_COMMIT; + mbi.Protect = 0; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10), + memory_info); + + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(result[0].base(), 10u); + EXPECT_EQ(result[0].size(), 5u); +} + +TEST(ProcessInfo, PageNoAccessIsInaccessible) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 0; + mbi.RegionSize = 10; + mbi.State = MEM_COMMIT; + mbi.Protect = PAGE_NOACCESS; + memory_info.push_back(mbi); + + mbi.BaseAddress = 10; + mbi.RegionSize = 20; + mbi.State = MEM_COMMIT; + mbi.Protect = 0; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10), + memory_info); + + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(result[0].base(), 10u); + EXPECT_EQ(result[0].size(), 5u); +} + +TEST(ProcessInfo, AccessibleRangesCoalesced) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 0; + mbi.RegionSize = 10; + mbi.State = MEM_FREE; + memory_info.push_back(mbi); + + mbi.BaseAddress = 10; + mbi.RegionSize = 2; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + mbi.BaseAddress = 12; + mbi.RegionSize = 5; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(11, 4), + memory_info); + + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(result[0].base(), 11u); + EXPECT_EQ(result[0].size(), 4u); +} + +TEST(ProcessInfo, AccessibleRangesMiddleUnavailable) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 0; + mbi.RegionSize = 10; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + mbi.BaseAddress = 10; + mbi.RegionSize = 5; + mbi.State = MEM_FREE; + memory_info.push_back(mbi); + + mbi.BaseAddress = 15; + mbi.RegionSize = 100; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 45), + memory_info); + + ASSERT_EQ(result.size(), 2u); + EXPECT_EQ(result[0].base(), 5u); + EXPECT_EQ(result[0].size(), 5u); + EXPECT_EQ(result[1].base(), 15u); + EXPECT_EQ(result[1].size(), 35u); +} + +TEST(ProcessInfo, RequestedBeforeMap) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 10; + mbi.RegionSize = 10; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap(CheckedRange<WinVMAddress, WinVMSize>(5, 10), + memory_info); + + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(result[0].base(), 10u); + EXPECT_EQ(result[0].size(), 5u); +} + +TEST(ProcessInfo, RequestedAfterMap) { + ProcessInfo::MemoryBasicInformation64Vector memory_info; + MEMORY_BASIC_INFORMATION64 mbi = {0}; + + mbi.BaseAddress = 10; + mbi.RegionSize = 10; + mbi.State = MEM_COMMIT; + memory_info.push_back(mbi); + + std::vector<CheckedRange<WinVMAddress, WinVMSize>> result = + GetReadableRangesOfMemoryMap( + CheckedRange<WinVMAddress, WinVMSize>(15, 100), memory_info); + + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(result[0].base(), 15u); + EXPECT_EQ(result[0].size(), 5u); +} + +TEST(ProcessInfo, ReadableRanges) { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + + const size_t kBlockSize = system_info.dwPageSize; + + // Allocate 6 pages, and then commit the second, fourth, and fifth, and mark + // two as committed, but PAGE_NOACCESS, so we have a setup like this: + // 0 1 2 3 4 5 + // +-----------------------------------------------+ + // | ????? | | xxxxx | | | ????? | + // +-----------------------------------------------+ + void* reserve_region = + VirtualAlloc(nullptr, kBlockSize * 6, MEM_RESERVE, PAGE_READWRITE); + ASSERT_TRUE(reserve_region); + uintptr_t reserved_as_int = reinterpret_cast<uintptr_t>(reserve_region); + void* readable1 = + VirtualAlloc(reinterpret_cast<void*>(reserved_as_int + kBlockSize), + kBlockSize, + MEM_COMMIT, + PAGE_READWRITE); + ASSERT_TRUE(readable1); + void* readable2 = + VirtualAlloc(reinterpret_cast<void*>(reserved_as_int + (kBlockSize * 3)), + kBlockSize * 2, + MEM_COMMIT, + PAGE_READWRITE); + ASSERT_TRUE(readable2); + + void* no_access = + VirtualAlloc(reinterpret_cast<void*>(reserved_as_int + (kBlockSize * 2)), + kBlockSize, + MEM_COMMIT, + PAGE_NOACCESS); + ASSERT_TRUE(no_access); + + HANDLE current_process = GetCurrentProcess(); + ProcessInfo info; + info.Initialize(current_process); + auto ranges = info.GetReadableRanges( + CheckedRange<WinVMAddress, WinVMSize>(reserved_as_int, kBlockSize * 6)); + + ASSERT_EQ(ranges.size(), 2u); + EXPECT_EQ(ranges[0].base(), reserved_as_int + kBlockSize); + EXPECT_EQ(ranges[0].size(), kBlockSize); + EXPECT_EQ(ranges[1].base(), reserved_as_int + (kBlockSize * 3)); + EXPECT_EQ(ranges[1].size(), kBlockSize * 2); + + // Also make sure what we think we can read corresponds with what we can + // actually read. + std::unique_ptr<unsigned char[]> into(new unsigned char[kBlockSize * 6]); + SIZE_T bytes_read; + + EXPECT_TRUE(ReadProcessMemory( + current_process, readable1, into.get(), kBlockSize, &bytes_read)); + EXPECT_EQ(bytes_read, kBlockSize); + + EXPECT_TRUE(ReadProcessMemory( + current_process, readable2, into.get(), kBlockSize * 2, &bytes_read)); + EXPECT_EQ(bytes_read, kBlockSize * 2); + + EXPECT_FALSE(ReadProcessMemory( + current_process, no_access, into.get(), kBlockSize, &bytes_read)); + EXPECT_FALSE(ReadProcessMemory( + current_process, reserve_region, into.get(), kBlockSize, &bytes_read)); + EXPECT_FALSE(ReadProcessMemory(current_process, + reserve_region, + into.get(), + kBlockSize * 6, + &bytes_read)); +} + +TEST(ProcessInfo, Handles) { + ScopedTempDir temp_dir; + + ScopedFileHandle file(LoggingOpenFileForWrite( + temp_dir.path().Append(FILE_PATH_LITERAL("test_file")), + FileWriteMode::kTruncateOrCreate, + FilePermissions::kWorldReadable)); + ASSERT_TRUE(file.is_valid()); + + SECURITY_ATTRIBUTES security_attributes = {0}; + security_attributes.nLength = sizeof(security_attributes); + security_attributes.bInheritHandle = true; + ScopedFileHandle inherited_file(CreateFile( + temp_dir.path().Append(FILE_PATH_LITERAL("inheritable")).value().c_str(), + GENERIC_WRITE, + 0, + &security_attributes, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + nullptr)); + ASSERT_TRUE(inherited_file.is_valid()); + + HKEY key; + ASSERT_EQ(RegOpenKeyEx( + HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft", 0, KEY_READ, &key), + ERROR_SUCCESS); + ScopedRegistryKey scoped_key(key); + ASSERT_TRUE(scoped_key.is_valid()); + + std::wstring mapping_name = + base::UTF8ToUTF16(base::StringPrintf("Local\\test_mapping_%lu_%s", + GetCurrentProcessId(), + RandomString().c_str())); + ScopedKernelHANDLE mapping(CreateFileMapping(INVALID_HANDLE_VALUE, + nullptr, + PAGE_READWRITE, + 0, + 1024, + mapping_name.c_str())); + ASSERT_TRUE(mapping.is_valid()) << ErrorMessage("CreateFileMapping"); + + ProcessInfo info; + info.Initialize(GetCurrentProcess()); + bool found_file_handle = false; + bool found_inherited_file_handle = false; + bool found_key_handle = false; + bool found_mapping_handle = false; + for (auto handle : info.Handles()) { + if (handle.handle == HandleToInt(file.get())) { + EXPECT_FALSE(found_file_handle); + found_file_handle = true; + EXPECT_EQ(handle.type_name, L"File"); + EXPECT_EQ(handle.handle_count, 1u); + EXPECT_NE(handle.pointer_count, 0u); + EXPECT_EQ(handle.granted_access & STANDARD_RIGHTS_ALL, + static_cast<uint32_t>(STANDARD_RIGHTS_READ | + STANDARD_RIGHTS_WRITE | SYNCHRONIZE)); + EXPECT_EQ(handle.attributes, 0u); + } + if (handle.handle == HandleToInt(inherited_file.get())) { + EXPECT_FALSE(found_inherited_file_handle); + found_inherited_file_handle = true; + EXPECT_EQ(handle.type_name, L"File"); + EXPECT_EQ(handle.handle_count, 1u); + EXPECT_NE(handle.pointer_count, 0u); + EXPECT_EQ(handle.granted_access & STANDARD_RIGHTS_ALL, + static_cast<uint32_t>(STANDARD_RIGHTS_READ | + STANDARD_RIGHTS_WRITE | SYNCHRONIZE)); + + // OBJ_INHERIT from ntdef.h, but including that conflicts with other + // headers. + constexpr uint32_t kObjInherit = 0x2; + EXPECT_EQ(handle.attributes, kObjInherit); + } + if (handle.handle == HandleToInt(scoped_key.get())) { + EXPECT_FALSE(found_key_handle); + found_key_handle = true; + EXPECT_EQ(handle.type_name, L"Key"); + EXPECT_EQ(handle.handle_count, 1u); + EXPECT_NE(handle.pointer_count, 0u); + EXPECT_EQ(handle.granted_access & STANDARD_RIGHTS_ALL, + static_cast<uint32_t>(STANDARD_RIGHTS_READ)); + EXPECT_EQ(handle.attributes, 0u); + } + if (handle.handle == HandleToInt(mapping.get())) { + EXPECT_FALSE(found_mapping_handle); + found_mapping_handle = true; + EXPECT_EQ(handle.type_name, L"Section"); + EXPECT_EQ(handle.handle_count, 1u); + EXPECT_NE(handle.pointer_count, 0u); + EXPECT_EQ(handle.granted_access & STANDARD_RIGHTS_ALL, + static_cast<uint32_t>(DELETE | READ_CONTROL | WRITE_DAC | + WRITE_OWNER | STANDARD_RIGHTS_READ | + STANDARD_RIGHTS_WRITE)); + EXPECT_EQ(handle.attributes, 0u); + } + } + EXPECT_TRUE(found_file_handle); + EXPECT_TRUE(found_inherited_file_handle); + EXPECT_TRUE(found_key_handle); + EXPECT_TRUE(found_mapping_handle); +} + +TEST(ProcessInfo, OutOfRangeCheck) { + constexpr size_t kAllocationSize = 12345; + std::unique_ptr<char[]> safe_memory(new char[kAllocationSize]); + + ProcessInfo info; + info.Initialize(GetCurrentProcess()); + + EXPECT_TRUE( + info.LoggingRangeIsFullyReadable(CheckedRange<WinVMAddress, WinVMSize>( + FromPointerCast<WinVMAddress>(safe_memory.get()), kAllocationSize))); + EXPECT_FALSE(info.LoggingRangeIsFullyReadable( + CheckedRange<WinVMAddress, WinVMSize>(0, 1024))); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/process_info_test_child.cc b/src/third_party/crashpad/util/win/process_info_test_child.cc new file mode 100644 index 0000000..1de1e8b --- /dev/null +++ b/src/third_party/crashpad/util/win/process_info_test_child.cc
@@ -0,0 +1,109 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <intrin.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <wchar.h> +#include <windows.h> +#include <winternl.h> + +namespace { + +bool UnicodeStringEndsWithCaseInsensitive(const UNICODE_STRING& us, + const wchar_t* ends_with) { + const size_t len = wcslen(ends_with); + // Recall that UNICODE_STRING.Length is in bytes, not characters. + const size_t us_len_in_chars = us.Length / sizeof(wchar_t); + if (us_len_in_chars < len) + return false; + return _wcsnicmp(&us.Buffer[us_len_in_chars - len], ends_with, len) == 0; +} + +} // namespace + +// A simple binary to be loaded and inspected by ProcessInfo. +int wmain(int argc, wchar_t** argv) { + if (argc != 2) + abort(); + + // Get a handle to the event we use to communicate with our parent. + HANDLE done_event = CreateEvent(nullptr, true, false, argv[1]); + if (!done_event) + abort(); + + // Load an unusual module (that we don't depend upon) so we can do an + // existence check. It's also important that these DLLs don't depend on + // any other DLLs, otherwise there'll be additional modules in the list, which + // the test expects not to be there. + if (!LoadLibrary(L"lz32.dll")) + abort(); + + // Load another unusual module so we can destroy its FullDllName field in the + // PEB to test corrupted name reads. + static constexpr wchar_t kCorruptableDll[] = L"kbdurdu.dll"; + if (!LoadLibrary(kCorruptableDll)) + abort(); + + // Find and corrupt the buffer pointer to the name in the PEB. + HINSTANCE ntdll = GetModuleHandle(L"ntdll.dll"); + decltype(NtQueryInformationProcess)* nt_query_information_process = + reinterpret_cast<decltype(NtQueryInformationProcess)*>( + GetProcAddress(ntdll, "NtQueryInformationProcess")); + if (!nt_query_information_process) + abort(); + + PROCESS_BASIC_INFORMATION pbi; + if (nt_query_information_process(GetCurrentProcess(), + ProcessBasicInformation, + &pbi, + sizeof(pbi), + nullptr) < 0) { + abort(); + } + + PEB_LDR_DATA* ldr = pbi.PebBaseAddress->Ldr; + LIST_ENTRY* head = &ldr->InMemoryOrderModuleList; + LIST_ENTRY* next = head->Flink; + while (next != head) { + LDR_DATA_TABLE_ENTRY* entry = + CONTAINING_RECORD(next, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); + if (UnicodeStringEndsWithCaseInsensitive(entry->FullDllName, + kCorruptableDll)) { + // Corrupt the pointer to the name. + entry->FullDllName.Buffer = 0; + } + next = next->Flink; + } + + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + if (out == INVALID_HANDLE_VALUE) + abort(); + // We just want any valid address that's known to be code. + uint64_t code_address = reinterpret_cast<uint64_t>(_ReturnAddress()); + DWORD bytes_written; + if (!WriteFile( + out, &code_address, sizeof(code_address), &bytes_written, nullptr) || + bytes_written != sizeof(code_address)) { + abort(); + } + + if (WaitForSingleObject(done_event, INFINITE) != WAIT_OBJECT_0) + abort(); + + CloseHandle(done_event); + + return EXIT_SUCCESS; +}
diff --git a/src/third_party/crashpad/util/win/process_structs.h b/src/third_party/crashpad/util/win/process_structs.h new file mode 100644 index 0000000..d9b5b2f --- /dev/null +++ b/src/third_party/crashpad/util/win/process_structs.h
@@ -0,0 +1,515 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_PROCESS_STRUCTS_H_ +#define CRASHPAD_UTIL_WIN_PROCESS_STRUCTS_H_ + +#include <windows.h> + +namespace crashpad { +namespace process_types { + +namespace internal { + +struct Traits32 { + using Pad = DWORD; + using UnsignedIntegral = DWORD; + using Pointer = DWORD; +}; + +struct Traits64 { + using Pad = DWORD64; + using UnsignedIntegral = DWORD64; + using Pointer = DWORD64; +}; + +} // namespace internal + +//! \{ + +//! \brief Selected structures from winternl.h, ntddk.h, and `dt ntdll!xxx`, +//! customized to have both x86 and x64 sizes available. +//! +//! The structure and field names follow the Windows names for clarity. We do, +//! however, use plain integral types rather than pointer types. This is both +//! easier to define, and avoids accidentally treating them as pointers into the +//! current address space. +//! +//! The templates below should be instantiated with either internal::Traits32 +//! for structures targeting x86, or internal::Traits64 for x64. + +// We set packing to 1 so that we can explicitly control the layout to make it +// match the OS defined structures. +#pragma pack(push, 1) + +template <class Traits> +struct PROCESS_BASIC_INFORMATION { + union { + DWORD ExitStatus; + typename Traits::Pad padding_for_x64_0; + }; + typename Traits::Pointer PebBaseAddress; + typename Traits::UnsignedIntegral AffinityMask; + union { + DWORD BasePriority; + typename Traits::Pad padding_for_x64_1; + }; + typename Traits::UnsignedIntegral UniqueProcessId; + typename Traits::UnsignedIntegral InheritedFromUniqueProcessId; +}; + +template <class Traits> +struct LIST_ENTRY { + typename Traits::Pointer Flink; + typename Traits::Pointer Blink; +}; + +template <class Traits> +struct UNICODE_STRING { + union { + struct { + USHORT Length; + USHORT MaximumLength; + }; + typename Traits::Pad padding_for_x64; + }; + typename Traits::Pointer Buffer; +}; + +template <class Traits> +struct PEB_LDR_DATA { + ULONG Length; + DWORD Initialized; + typename Traits::Pointer SsHandle; + LIST_ENTRY<Traits> InLoadOrderModuleList; + LIST_ENTRY<Traits> InMemoryOrderModuleList; + LIST_ENTRY<Traits> InInitializationOrderModuleList; +}; + +template <class Traits> +struct LDR_DATA_TABLE_ENTRY { + LIST_ENTRY<Traits> InLoadOrderLinks; + LIST_ENTRY<Traits> InMemoryOrderLinks; + LIST_ENTRY<Traits> InInitializationOrderLinks; + typename Traits::Pointer DllBase; + typename Traits::Pointer EntryPoint; + union { + ULONG SizeOfImage; + typename Traits::Pad padding_for_x64; + }; + UNICODE_STRING<Traits> FullDllName; + UNICODE_STRING<Traits> BaseDllName; + ULONG Flags; + USHORT ObsoleteLoadCount; + USHORT TlsIndex; + LIST_ENTRY<Traits> HashLinks; + ULONG TimeDateStamp; +}; + +template <class Traits> +struct CURDIR { + UNICODE_STRING<Traits> DosPath; + typename Traits::Pointer Handle; +}; + +template <class Traits> +struct STRING { + union { + struct { + USHORT Length; + USHORT MaximumLength; + }; + typename Traits::Pad padding_for_x64; + }; + typename Traits::Pointer Buffer; +}; + +template <class Traits> +struct RTL_DRIVE_LETTER_CURDIR { + WORD Flags; + WORD Length; + DWORD TimeStamp; + STRING<Traits> DosPath; +}; + +template <class Traits> +struct RTL_USER_PROCESS_PARAMETERS { + DWORD MaximumLength; + DWORD Length; + DWORD Flags; + DWORD DebugFlags; + typename Traits::Pointer ConsoleHandle; + union { + DWORD ConsoleFlags; + typename Traits::Pad padding_for_x64_0; + }; + typename Traits::Pointer StandardInput; + typename Traits::Pointer StandardOutput; + typename Traits::Pointer StandardError; + CURDIR<Traits> CurrentDirectory; + UNICODE_STRING<Traits> DllPath; + UNICODE_STRING<Traits> ImagePathName; + UNICODE_STRING<Traits> CommandLine; + typename Traits::Pointer Environment; + DWORD StartingX; + DWORD StartingY; + DWORD CountX; + DWORD CountY; + DWORD CountCharsX; + DWORD CountCharsY; + DWORD FillAttribute; + DWORD WindowFlags; + union { + DWORD ShowWindowFlags; + typename Traits::Pad padding_for_x64_1; + }; + UNICODE_STRING<Traits> WindowTitle; + UNICODE_STRING<Traits> DesktopInfo; + UNICODE_STRING<Traits> ShellInfo; + UNICODE_STRING<Traits> RuntimeData; + RTL_DRIVE_LETTER_CURDIR<Traits> CurrentDirectores[32]; // sic. + ULONG EnvironmentSize; +}; + +template <class T> +struct GdiHandleBufferCountForBitness; + +template <> +struct GdiHandleBufferCountForBitness<internal::Traits32> { + enum { value = 34 }; +}; +template <> +struct GdiHandleBufferCountForBitness<internal::Traits64> { + enum { value = 60 }; +}; + +template <class Traits> +struct PEB { + union { + struct { + BYTE InheritedAddressSpace; + BYTE ReadImageFileExecOptions; + BYTE BeingDebugged; + BYTE BitField; + }; + typename Traits::Pad padding_for_x64_0; + }; + typename Traits::Pointer Mutant; + typename Traits::Pointer ImageBaseAddress; + typename Traits::Pointer Ldr; + typename Traits::Pointer ProcessParameters; + typename Traits::Pointer SubSystemData; + typename Traits::Pointer ProcessHeap; + typename Traits::Pointer FastPebLock; + typename Traits::Pointer AtlThunkSListPtr; + typename Traits::Pointer IFEOKey; + union { + DWORD CrossProcessFlags; + typename Traits::Pad padding_for_x64_1; + }; + typename Traits::Pointer KernelCallbackTable; + DWORD SystemReserved; + DWORD AtlThunkSListPtr32; + typename Traits::Pointer ApiSetMap; + union { + DWORD TlsExpansionCounter; + typename Traits::Pad padding_for_x64_2; + }; + typename Traits::Pointer TlsBitmap; + DWORD TlsBitmapBits[2]; + typename Traits::Pointer ReadOnlySharedMemoryBase; + typename Traits::Pointer SparePvoid0; + typename Traits::Pointer ReadOnlyStaticServerData; + typename Traits::Pointer AnsiCodePageData; + typename Traits::Pointer OemCodePageData; + typename Traits::Pointer UnicodeCaseTableData; + DWORD NumberOfProcessors; + DWORD NtGlobalFlag; + DWORD alignment_for_x86; + LARGE_INTEGER CriticalSectionTimeout; + typename Traits::UnsignedIntegral HeapSegmentReserve; + typename Traits::UnsignedIntegral HeapSegmentCommit; + typename Traits::UnsignedIntegral HeapDeCommitTotalFreeThreshold; + typename Traits::UnsignedIntegral HeapDeCommitFreeBlockThreshold; + DWORD NumberOfHeaps; + DWORD MaximumNumberOfHeaps; + typename Traits::Pointer ProcessHeaps; + typename Traits::Pointer GdiSharedHandleTable; + typename Traits::Pointer ProcessStarterHelper; + DWORD GdiDCAttributeList; + typename Traits::Pointer LoaderLock; + DWORD OSMajorVersion; + DWORD OSMinorVersion; + WORD OSBuildNumber; + WORD OSCSDVersion; + DWORD OSPlatformId; + DWORD ImageSubsystem; + DWORD ImageSubsystemMajorVersion; + union { + DWORD ImageSubsystemMinorVersion; + typename Traits::Pad padding_for_x64_3; + }; + typename Traits::UnsignedIntegral ActiveProcessAffinityMask; + DWORD GdiHandleBuffer[GdiHandleBufferCountForBitness<Traits>::value]; + typename Traits::Pointer PostProcessInitRoutine; + typename Traits::Pointer TlsExpansionBitmap; + DWORD TlsExpansionBitmapBits[32]; + union { + DWORD SessionId; + typename Traits::Pad padding_for_x64_4; + }; + ULARGE_INTEGER AppCompatFlags; + ULARGE_INTEGER AppCompatFlagsUser; + typename Traits::Pointer pShimData; + typename Traits::Pointer AppCompatInfo; + UNICODE_STRING<Traits> CSDVersion; + typename Traits::Pointer ActivationContextData; + typename Traits::Pointer ProcessAssemblyStorageMap; + typename Traits::Pointer SystemDefaultActivationContextData; + typename Traits::Pointer SystemAssemblyStorageMap; + typename Traits::UnsignedIntegral MinimumStackCommit; + typename Traits::Pointer FlsCallback; + LIST_ENTRY<Traits> FlsListHead; + typename Traits::Pointer FlsBitmap; + DWORD FlsBitmapBits[4]; + DWORD FlsHighIndex; +}; + +template <class Traits> +struct NT_TIB { + union { + // See https://msdn.microsoft.com/library/dn424783.aspx. + typename Traits::Pointer Wow64Teb; + struct { + typename Traits::Pointer ExceptionList; + typename Traits::Pointer StackBase; + typename Traits::Pointer StackLimit; + typename Traits::Pointer SubSystemTib; + union { + typename Traits::Pointer FiberData; + BYTE Version[4]; + }; + typename Traits::Pointer ArbitraryUserPointer; + typename Traits::Pointer Self; + }; + }; +}; + +// See https://msdn.microsoft.com/library/gg750647.aspx. +template <class Traits> +struct CLIENT_ID { + typename Traits::Pointer UniqueProcess; + typename Traits::Pointer UniqueThread; +}; + +// This is a partial definition of the TEB, as we do not currently use many +// fields of it. See https://nirsoft.net/kernel_struct/vista/TEB.html, and the +// (arch-specific) definition of _TEB in winternl.h. +template <class Traits> +struct TEB { + NT_TIB<Traits> NtTib; + typename Traits::Pointer EnvironmentPointer; + CLIENT_ID<Traits> ClientId; + typename Traits::Pointer ActiveRpcHandle; + typename Traits::Pointer ThreadLocalStoragePointer; + typename Traits::Pointer ProcessEnvironmentBlock; + typename Traits::Pointer RemainderOfReserved2[399]; + BYTE Reserved3[1952]; + typename Traits::Pointer TlsSlots[64]; + BYTE Reserved4[8]; + typename Traits::Pointer Reserved5[26]; + typename Traits::Pointer ReservedForOle; + typename Traits::Pointer Reserved6[4]; + typename Traits::Pointer TlsExpansionSlots; +}; + +// See https://msdn.microsoft.com/library/gg750724.aspx. +template <class Traits> +struct SYSTEM_THREAD_INFORMATION { + union { + struct { + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER CreateTime; + union { + ULONG WaitTime; + typename Traits::Pad padding_for_x64_0; + }; + typename Traits::Pointer StartAddress; + CLIENT_ID<Traits> ClientId; + LONG Priority; + LONG BasePriority; + ULONG ContextSwitches; + ULONG ThreadState; + union { + ULONG WaitReason; + typename Traits::Pad padding_for_x64_1; + }; + }; + LARGE_INTEGER alignment_for_x86[8]; + }; +}; + +// There's an extra field in the x64 VM_COUNTERS (or maybe it's VM_COUNTERS_EX, +// it's not clear), so we just make separate specializations for 32/64. +template <class Traits> +struct VM_COUNTERS {}; + +template <> +struct VM_COUNTERS<internal::Traits32> { + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + ULONG PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; +}; + +template <> +struct VM_COUNTERS<internal::Traits64> { + SIZE_T PeakVirtualSize; + SIZE_T VirtualSize; + union { + ULONG PageFaultCount; + internal::Traits64::Pad padding_for_x64; + }; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; + SIZE_T PrivateUsage; +}; + +// https://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/System%20Information/Structures/SYSTEM_PROCESS_INFORMATION.html +template <class Traits> +struct SYSTEM_PROCESS_INFORMATION { + ULONG NextEntryOffset; + ULONG NumberOfThreads; + LARGE_INTEGER WorkingSetPrivateSize; + ULONG HardFaultCount; + ULONG NumberOfThreadsHighWatermark; + ULONGLONG CycleTime; + LARGE_INTEGER CreateTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER KernelTime; + UNICODE_STRING<Traits> ImageName; + union { + LONG BasePriority; + typename Traits::Pad padding_for_x64_0; + }; + union { + DWORD UniqueProcessId; + typename Traits::Pad padding_for_x64_1; + }; + union { + DWORD InheritedFromUniqueProcessId; + typename Traits::Pad padding_for_x64_2; + }; + ULONG HandleCount; + ULONG SessionId; + typename Traits::Pointer UniqueProcessKey; + union { + VM_COUNTERS<Traits> VirtualMemoryCounters; + LARGE_INTEGER alignment_for_x86[6]; + }; + IO_COUNTERS IoCounters; + SYSTEM_THREAD_INFORMATION<Traits> Threads[1]; +}; + +// https://undocumented.ntinternals.net/source/usermode/structures/THREAD_BASIC_INFORMATION.html +template <class Traits> +struct THREAD_BASIC_INFORMATION { + union { + LONG ExitStatus; + typename Traits::Pad padding_for_x64_0; + }; + typename Traits::Pointer TebBaseAddress; + CLIENT_ID<Traits> ClientId; + typename Traits::Pointer AffinityMask; + ULONG Priority; + LONG BasePriority; +}; + +template <class Traits> +struct EXCEPTION_POINTERS { + typename Traits::Pointer ExceptionRecord; + typename Traits::Pointer ContextRecord; +}; + +using EXCEPTION_POINTERS32 = EXCEPTION_POINTERS<internal::Traits32>; +using EXCEPTION_POINTERS64 = EXCEPTION_POINTERS<internal::Traits64>; + +// This is defined in winnt.h, but not for cross-bitness. +template <class Traits> +struct RTL_CRITICAL_SECTION { + typename Traits::Pointer DebugInfo; + LONG LockCount; + LONG RecursionCount; + typename Traits::Pointer OwningThread; + typename Traits::Pointer LockSemaphore; + typename Traits::UnsignedIntegral SpinCount; +}; + +template <class Traits> +struct RTL_CRITICAL_SECTION_DEBUG { + union { + struct { + WORD Type; + WORD CreatorBackTraceIndex; + }; + typename Traits::Pad alignment_for_x64; + }; + typename Traits::Pointer CriticalSection; + LIST_ENTRY<Traits> ProcessLocksList; + DWORD EntryCount; + DWORD ContentionCount; + DWORD Flags; + WORD CreatorBackTraceIndexHigh; + WORD SpareWORD; +}; + +struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { + void* Object; + ULONG_PTR UniqueProcessId; + HANDLE HandleValue; + ULONG GrantedAccess; + USHORT CreatorBackTraceIndex; + USHORT ObjectTypeIndex; + ULONG HandleAttributes; + ULONG Reserved; +}; + +struct SYSTEM_HANDLE_INFORMATION_EX { + ULONG_PTR NumberOfHandles; + ULONG_PTR Reserved; + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1]; +}; + +#pragma pack(pop) + +//! \} + +} // namespace process_types +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_PROCESS_STRUCTS_H_
diff --git a/src/third_party/crashpad/util/win/registration_protocol_win.cc b/src/third_party/crashpad/util/win/registration_protocol_win.cc new file mode 100644 index 0000000..64ed518 --- /dev/null +++ b/src/third_party/crashpad/util/win/registration_protocol_win.cc
@@ -0,0 +1,271 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/registration_protocol_win.h" + +#include <windows.h> +#include <aclapi.h> +#include <sddl.h> +#include <stddef.h> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "util/win/exception_handler_server.h" +#include "util/win/loader_lock.h" +#include "util/win/scoped_handle.h" +#include "util/win/scoped_local_alloc.h" + +namespace crashpad { + +namespace { + +void* GetSecurityDescriptorWithUser(const base::char16* sddl_string, + size_t* size) { + if (size) + *size = 0; + + PSECURITY_DESCRIPTOR base_sec_desc; + if (!ConvertStringSecurityDescriptorToSecurityDescriptor( + sddl_string, SDDL_REVISION_1, &base_sec_desc, nullptr)) { + PLOG(ERROR) << "ConvertStringSecurityDescriptorToSecurityDescriptor"; + return nullptr; + } + + ScopedLocalAlloc base_sec_desc_owner(base_sec_desc); + EXPLICIT_ACCESS access; + wchar_t username[] = L"CURRENT_USER"; + BuildExplicitAccessWithName( + &access, username, GENERIC_ALL, GRANT_ACCESS, NO_INHERITANCE); + + PSECURITY_DESCRIPTOR user_sec_desc; + ULONG user_sec_desc_size; + DWORD error = BuildSecurityDescriptor(nullptr, + nullptr, + 1, + &access, + 0, + nullptr, + base_sec_desc, + &user_sec_desc_size, + &user_sec_desc); + if (error != ERROR_SUCCESS) { + SetLastError(error); + PLOG(ERROR) << "BuildSecurityDescriptor"; + return nullptr; + } + + *size = user_sec_desc_size; + return user_sec_desc; +} + +} // namespace + +bool SendToCrashHandlerServer(const base::string16& pipe_name, + const ClientToServerMessage& message, + ServerToClientMessage* response) { + // Retry CreateFile() in a loop. If the handler isn’t actively waiting in + // ConnectNamedPipe() on a pipe instance because it’s busy doing something + // else, CreateFile() will fail with ERROR_PIPE_BUSY. WaitNamedPipe() waits + // until a pipe instance is ready, but there’s no way to wait for this + // condition and atomically open the client side of the pipe in a single + // operation. CallNamedPipe() implements similar retry logic to this, also in + // user-mode code. + // + // This loop is only intended to retry on ERROR_PIPE_BUSY. Notably, if the + // handler is so lazy that it hasn’t even called CreateNamedPipe() yet, + // CreateFile() will fail with ERROR_FILE_NOT_FOUND, and this function is + // expected to fail without retrying anything. If the handler is started at + // around the same time as its client, something external to this code must be + // done to guarantee correct ordering. When the client starts the handler + // itself, CrashpadClient::StartHandler() provides this synchronization. + for (;;) { + ScopedFileHANDLE pipe( + CreateFile(pipe_name.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + nullptr, + OPEN_EXISTING, + SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, + nullptr)); + if (!pipe.is_valid()) { + if (GetLastError() != ERROR_PIPE_BUSY) { + PLOG(ERROR) << "CreateFile"; + return false; + } + + if (!WaitNamedPipe(pipe_name.c_str(), NMPWAIT_WAIT_FOREVER)) { + PLOG(ERROR) << "WaitNamedPipe"; + return false; + } + + continue; + } + + DWORD mode = PIPE_READMODE_MESSAGE; + if (!SetNamedPipeHandleState(pipe.get(), &mode, nullptr, nullptr)) { + PLOG(ERROR) << "SetNamedPipeHandleState"; + return false; + } + DWORD bytes_read = 0; + BOOL result = TransactNamedPipe( + pipe.get(), + // This is [in], but is incorrectly declared non-const. + const_cast<ClientToServerMessage*>(&message), + sizeof(message), + response, + sizeof(*response), + &bytes_read, + nullptr); + if (!result) { + PLOG(ERROR) << "TransactNamedPipe"; + return false; + } + if (bytes_read != sizeof(*response)) { + LOG(ERROR) << "TransactNamedPipe: expected " << sizeof(*response) + << ", observed " << bytes_read; + return false; + } + return true; + } +} + +HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name, + bool first_instance) { + SECURITY_ATTRIBUTES security_attributes; + SECURITY_ATTRIBUTES* security_attributes_pointer = nullptr; + + if (first_instance) { + // Pre-Vista does not have integrity levels. + const DWORD version = GetVersion(); + const DWORD major_version = LOBYTE(LOWORD(version)); + const bool is_vista_or_later = major_version >= 6; + if (is_vista_or_later) { + memset(&security_attributes, 0, sizeof(security_attributes)); + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.lpSecurityDescriptor = + const_cast<void*>(GetSecurityDescriptorForNamedPipeInstance(nullptr)); + security_attributes.bInheritHandle = TRUE; + security_attributes_pointer = &security_attributes; + } + } + + return CreateNamedPipe( + pipe_name.c_str(), + PIPE_ACCESS_DUPLEX | (first_instance ? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + ExceptionHandlerServer::kPipeInstances, + 512, + 512, + 0, + security_attributes_pointer); +} + +const void* GetFallbackSecurityDescriptorForNamedPipeInstance(size_t* size) { + // Mandatory Label, no ACE flags, no ObjectType, integrity level untrusted is + // "S:(ML;;;;;S-1-16-0)". This static security descriptor is used as a + // fallback if GetSecurityDescriptorWithUser fails, to avoid losing crashes + // from non-AppContainer sandboxed applications. + +#pragma pack(push, 1) + static constexpr struct SecurityDescriptorBlob { + // See https://msdn.microsoft.com/library/cc230366.aspx. + SECURITY_DESCRIPTOR_RELATIVE sd_rel; + struct { + ACL acl; + struct { + // This is equivalent to SYSTEM_MANDATORY_LABEL_ACE, but there's no + // DWORD offset to the SID, instead it's inline. + ACE_HEADER header; + ACCESS_MASK mask; + SID sid; + } ace[1]; + } sacl; + } kSecDescBlob = { + // sd_rel. + { + SECURITY_DESCRIPTOR_REVISION1, // Revision. + 0x00, // Sbz1. + SE_SELF_RELATIVE | SE_SACL_PRESENT, // Control. + 0, // OffsetOwner. + 0, // OffsetGroup. + offsetof(SecurityDescriptorBlob, sacl), // OffsetSacl. + 0, // OffsetDacl. + }, + + // sacl. + { + // acl. + { + ACL_REVISION, // AclRevision. + 0, // Sbz1. + sizeof(kSecDescBlob.sacl), // AclSize. + static_cast<WORD>( + base::size(kSecDescBlob.sacl.ace)), // AceCount. + 0, // Sbz2. + }, + + // ace[0]. + { + { + // header. + { + SYSTEM_MANDATORY_LABEL_ACE_TYPE, // AceType. + 0, // AceFlags. + sizeof(kSecDescBlob.sacl.ace[0]), // AceSize. + }, + + // mask. + 0, + + // sid. + { + SID_REVISION, // Revision. + // SubAuthorityCount. + static_cast<BYTE>(base::size( + kSecDescBlob.sacl.ace[0].sid.SubAuthority)), + // IdentifierAuthority. + {SECURITY_MANDATORY_LABEL_AUTHORITY}, + {SECURITY_MANDATORY_UNTRUSTED_RID}, // SubAuthority. + }, + }, + }, + }, + }; +#pragma pack(pop) + + if (size) + *size = sizeof(kSecDescBlob); + return reinterpret_cast<const void*>(&kSecDescBlob); +} + +const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size) { + CHECK(!IsThreadInLoaderLock()); + + // Get a security descriptor which grants the current user and SYSTEM full + // access to the named pipe. Also grant AppContainer RW access through the ALL + // APPLICATION PACKAGES SID (S-1-15-2-1). Finally add an Untrusted Mandatory + // Label for non-AppContainer sandboxed users. + static size_t sd_size; + static void* sec_desc = GetSecurityDescriptorWithUser( + L"D:(A;;GA;;;SY)(A;;GWGR;;;S-1-15-2-1)S:(ML;;;;;S-1-16-0)", &sd_size); + + if (!sec_desc) + return GetFallbackSecurityDescriptorForNamedPipeInstance(size); + + if (size) + *size = sd_size; + return sec_desc; +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/registration_protocol_win.h b/src/third_party/crashpad/util/win/registration_protocol_win.h new file mode 100644 index 0000000..ef8bebc --- /dev/null +++ b/src/third_party/crashpad/util/win/registration_protocol_win.h
@@ -0,0 +1,175 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_REGISTRATION_PROTOCOL_WIN_H_ +#define CRASHPAD_UTIL_WIN_REGISTRATION_PROTOCOL_WIN_H_ + +#include <windows.h> +#include <stdint.h> + +#include "base/strings/string16.h" +#include "util/win/address_types.h" + +namespace crashpad { + +#pragma pack(push, 1) + +//! \brief Structure read out of the client process by the crash handler when an +//! exception occurs. +struct ExceptionInformation { + //! \brief The address of an EXCEPTION_POINTERS structure in the client + //! process that describes the exception. + WinVMAddress exception_pointers; + + //! \brief The thread on which the exception happened. + DWORD thread_id; +}; + +//! \brief A client registration request. +struct RegistrationRequest { + //! \brief The expected value of `version`. This should be changed whenever + //! the messages or ExceptionInformation are modified incompatibly. + enum { kMessageVersion = 1 }; + + //! \brief Version field to detect skew between client and server. Should be + //! set to kMessageVersion. + int version; + + //! \brief The PID of the client process. + DWORD client_process_id; + + //! \brief The address, in the client process's address space, of an + //! ExceptionInformation structure, used when handling a crash dump + //! request. + WinVMAddress crash_exception_information; + + //! \brief The address, in the client process's address space, of an + //! ExceptionInformation structure, used when handling a non-crashing dump + //! request. + WinVMAddress non_crash_exception_information; + + //! \brief The address, in the client process's address space, of a + //! `CRITICAL_SECTION` allocated with a valid .DebugInfo field. This can + //! be accomplished by using + //! InitializeCriticalSectionWithDebugInfoIfPossible() or equivalent. This + //! value can be `0`, however then limited lock data will be available in + //! minidumps. + WinVMAddress critical_section_address; +}; + +//! \brief A message only sent to the server by itself to trigger shutdown. +struct ShutdownRequest { + //! \brief A randomly generated token used to validate the the shutdown + //! request was not sent from another process. + uint64_t token; +}; + +//! \brief The message passed from client to server by +//! SendToCrashHandlerServer(). +struct ClientToServerMessage { + //! \brief Indicates which field of the union is in use. + enum Type : uint32_t { + //! \brief For RegistrationRequest. + kRegister, + + //! \brief For ShutdownRequest. + kShutdown, + + //! \brief An empty message sent by the initial client in asynchronous mode. + //! No data is required, this just confirms that the server is ready to + //! accept client registrations. + kPing, + } type; + + union { + RegistrationRequest registration; + ShutdownRequest shutdown; + }; +}; + +//! \brief A client registration response. +struct RegistrationResponse { + //! \brief An event `HANDLE`, valid in the client process, that should be + //! signaled to request a crash report. Clients should convert the value + //! to a `HANDLE` by calling IntToHandle(). + int request_crash_dump_event; + + //! \brief An event `HANDLE`, valid in the client process, that should be + //! signaled to request a non-crashing dump be taken. Clients should + //! convert the value to a `HANDLE` by calling IntToHandle(). + int request_non_crash_dump_event; + + //! \brief An event `HANDLE`, valid in the client process, that will be + //! signaled by the server when the non-crashing dump is complete. Clients + //! should convert the value to a `HANDLE` by calling IntToHandle(). + int non_crash_dump_completed_event; +}; + +//! \brief The response sent back to the client via SendToCrashHandlerServer(). +union ServerToClientMessage { + RegistrationResponse registration; +}; + +#pragma pack(pop) + +//! \brief Connect over the given \a pipe_name, passing \a message to the +//! server, storing the server's reply into \a response. +//! +//! Typically clients will not use this directly, instead using +//! CrashpadClient::SetHandler(). +//! +//! \sa CrashpadClient::SetHandler() +bool SendToCrashHandlerServer(const base::string16& pipe_name, + const ClientToServerMessage& message, + ServerToClientMessage* response); + +//! \brief Wraps CreateNamedPipe() to create a single named pipe instance. +//! +//! \param[in] pipe_name The name to use for the pipe. +//! \param[in] first_instance If `true`, the named pipe instance will be +//! created with `FILE_FLAG_FIRST_PIPE_INSTANCE`. This ensures that the the +//! pipe name is not already in use when created. The first instance will be +//! created with an untrusted integrity SACL so instances of this pipe can +//! be connected to by processes of any integrity level. +HANDLE CreateNamedPipeInstance(const std::wstring& pipe_name, + bool first_instance); + +//! \brief Returns the `SECURITY_DESCRIPTOR` blob that will be used for creating +//! the connection pipe in CreateNamedPipeInstance(). +//! +//! This function is only exposed for testing. +//! +//! \param[out] size The size of the returned blob. May be `nullptr` if not +//! required. +//! +//! \return A pointer to a self-relative `SECURITY_DESCRIPTOR`. Ownership is not +//! transferred to the caller. +const void* GetSecurityDescriptorForNamedPipeInstance(size_t* size); + +//! \brief Returns the `SECURITY_DESCRIPTOR` blob that will be used for creating +//! the connection pipe in CreateNamedPipeInstance() if the full descriptor +//! can't be created. +//! +//! This function is only exposed for testing. +//! +//! \param[out] size The size of the returned blob. May be `nullptr` if not +//! required. +//! +//! \return A pointer to a self-relative `SECURITY_DESCRIPTOR`. Ownership is not +//! transferred to the caller. +const void* GetFallbackSecurityDescriptorForNamedPipeInstance(size_t* size); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_REGISTRATION_PROTOCOL_WIN_H_
diff --git a/src/third_party/crashpad/util/win/registration_protocol_win_test.cc b/src/third_party/crashpad/util/win/registration_protocol_win_test.cc new file mode 100644 index 0000000..334d7d8 --- /dev/null +++ b/src/third_party/crashpad/util/win/registration_protocol_win_test.cc
@@ -0,0 +1,157 @@ +// Copyright 2016 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/registration_protocol_win.h" + +#include <aclapi.h> +#include <sddl.h> +#include <string.h> +#include <windows.h> + +#include <vector> + +#include "base/logging.h" +#include "base/strings/string16.h" +#include "gtest/gtest.h" +#include "test/errors.h" +#include "util/win/scoped_handle.h" +#include "util/win/scoped_local_alloc.h" + +namespace crashpad { +namespace test { +namespace { + +base::string16 GetStringFromSid(PSID sid) { + LPWSTR sid_str; + if (!ConvertSidToStringSid(sid, &sid_str)) { + PLOG(ERROR) << "ConvertSidToStringSid"; + return base::string16(); + } + ScopedLocalAlloc sid_str_ptr(sid_str); + return sid_str; +} + +base::string16 GetUserSidString() { + HANDLE token_handle; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle)) { + PLOG(ERROR) << "OpenProcessToken"; + return base::string16(); + } + + ScopedKernelHANDLE token(token_handle); + DWORD user_size = 0; + GetTokenInformation(token.get(), TokenUser, nullptr, 0, &user_size); + if (user_size == 0) { + PLOG(ERROR) << "GetTokenInformation Size"; + return base::string16(); + } + + std::vector<char> user(user_size); + if (!GetTokenInformation( + token.get(), TokenUser, user.data(), user_size, &user_size)) { + PLOG(ERROR) << "GetTokenInformation"; + return base::string16(); + } + + TOKEN_USER* user_ptr = reinterpret_cast<TOKEN_USER*>(user.data()); + return GetStringFromSid(user_ptr->User.Sid); +} + +void CheckAce(PACL acl, + DWORD index, + BYTE check_ace_type, + ACCESS_MASK check_mask, + const base::string16& check_sid) { + ASSERT_FALSE(check_sid.empty()); + void* ace_ptr; + ASSERT_TRUE(GetAce(acl, index, &ace_ptr)); + + ACE_HEADER* header = static_cast<ACE_HEADER*>(ace_ptr); + ASSERT_EQ(check_ace_type, header->AceType); + ASSERT_EQ(0, header->AceFlags); + + PSID sid = nullptr; + ACCESS_MASK mask = 0; + switch (header->AceType) { + case ACCESS_ALLOWED_ACE_TYPE: { + ACCESS_ALLOWED_ACE* allowed_ace = + static_cast<ACCESS_ALLOWED_ACE*>(ace_ptr); + sid = &allowed_ace->SidStart; + mask = allowed_ace->Mask; + } break; + case SYSTEM_MANDATORY_LABEL_ACE_TYPE: { + SYSTEM_MANDATORY_LABEL_ACE* label_ace = + static_cast<SYSTEM_MANDATORY_LABEL_ACE*>(ace_ptr); + sid = &label_ace->SidStart; + mask = label_ace->Mask; + } break; + default: + NOTREACHED(); + break; + } + + ASSERT_EQ(check_mask, mask); + ASSERT_EQ(check_sid, GetStringFromSid(sid)); +} + +TEST(SecurityDescriptor, NamedPipeDefault) { + const void* sec_desc = GetSecurityDescriptorForNamedPipeInstance(nullptr); + + PACL acl; + BOOL acl_present; + BOOL acl_defaulted; + ASSERT_TRUE(GetSecurityDescriptorDacl( + const_cast<void*>(sec_desc), &acl_present, &acl, &acl_defaulted)); + ASSERT_EQ(3, acl->AceCount); + CheckAce(acl, 0, ACCESS_ALLOWED_ACE_TYPE, GENERIC_ALL, GetUserSidString()); + // Check SYSTEM user SID. + CheckAce(acl, 1, ACCESS_ALLOWED_ACE_TYPE, GENERIC_ALL, L"S-1-5-18"); + // Check ALL APPLICATION PACKAGES group SID. + CheckAce(acl, + 2, + ACCESS_ALLOWED_ACE_TYPE, + GENERIC_READ | GENERIC_WRITE, + L"S-1-15-2-1"); + + ASSERT_TRUE(GetSecurityDescriptorSacl( + const_cast<void*>(sec_desc), &acl_present, &acl, &acl_defaulted)); + ASSERT_EQ(1, acl->AceCount); + CheckAce(acl, 0, SYSTEM_MANDATORY_LABEL_ACE_TYPE, 0, L"S-1-16-0"); +} + +TEST(SecurityDescriptor, MatchesAdvapi32) { + // This security descriptor is built manually in the connection code to avoid + // calling the advapi32 functions. Verify that it returns the same thing as + // ConvertStringSecurityDescriptorToSecurityDescriptor() would. + + // Mandatory Label, no ACE flags, no ObjectType, integrity level + // untrusted. + static constexpr wchar_t kSddl[] = L"S:(ML;;;;;S-1-16-0)"; + PSECURITY_DESCRIPTOR sec_desc; + ULONG sec_desc_len; + ASSERT_TRUE(ConvertStringSecurityDescriptorToSecurityDescriptor( + kSddl, SDDL_REVISION_1, &sec_desc, &sec_desc_len)) + << ErrorMessage("ConvertStringSecurityDescriptorToSecurityDescriptor"); + ScopedLocalAlloc sec_desc_owner(sec_desc); + + size_t created_len; + const void* const created = + GetFallbackSecurityDescriptorForNamedPipeInstance(&created_len); + ASSERT_EQ(created_len, sec_desc_len); + EXPECT_EQ(memcmp(sec_desc, created, sec_desc_len), 0); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/safe_terminate_process.asm b/src/third_party/crashpad/util/win/safe_terminate_process.asm new file mode 100644 index 0000000..b219a9e --- /dev/null +++ b/src/third_party/crashpad/util/win/safe_terminate_process.asm
@@ -0,0 +1,74 @@ +; Copyright 2017 The Crashpad Authors. All rights reserved. +; +; Licensed under the Apache License, Version 2.0 (the "License"); +; you may not use this file except in compliance with the License. +; You may obtain a copy of the License at +; +; http://www.apache.org/licenses/LICENSE-2.0 +; +; Unless required by applicable law or agreed to in writing, software +; distributed under the License is distributed on an "AS IS" BASIS, +; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +; See the License for the specific language governing permissions and +; limitations under the License. + +; Detect ml64 assembling for x86_64 by checking for rax. +ifdef rax +_M_X64 equ 1 +else +_M_IX86 equ 1 +endif + +ifdef _M_IX86 +.586 +.xmm +.model flat + +includelib kernel32.lib + +extern __imp__TerminateProcess@8:proc + +; namespace crashpad { +; bool SafeTerminateProcess(HANDLE process, UINT exit_code); +; } // namespace crashpad +SAFETERMINATEPROCESS_SYMBOL equ ?SafeTerminateProcess@crashpad@@YA_NPAXI@Z + +_TEXT segment +public SAFETERMINATEPROCESS_SYMBOL + +SAFETERMINATEPROCESS_SYMBOL proc + + ; This function is written in assembler source because it’s important for it + ; to not be inlined, for it to allocate a stack frame, and most critically, + ; for it to not trust esp on return from TerminateProcess(). + ; __declspec(noinline) can prevent inlining and #pragma optimize("y", off) can + ; disable frame pointer omission, but there’s no way to force a C compiler to + ; distrust esp, and even if there was a way, it’d probably be fragile. + + push ebp + mov ebp, esp + + push [ebp+12] + push [ebp+8] + call dword ptr [__imp__TerminateProcess@8] + + ; Convert from BOOL to bool. + test eax, eax + setne al + + ; TerminateProcess() is supposed to be stdcall (callee clean-up), and esp and + ; ebp are expected to already be equal. But if it’s been patched badly by + ; something that’s cdecl (caller clean-up), this next move will get things + ; back on track. + mov esp, ebp + pop ebp + + ret + +SAFETERMINATEPROCESS_SYMBOL endp + +_TEXT ends + +endif + +end
diff --git a/src/third_party/crashpad/util/win/safe_terminate_process.h b/src/third_party/crashpad/util/win/safe_terminate_process.h new file mode 100644 index 0000000..7079eb2 --- /dev/null +++ b/src/third_party/crashpad/util/win/safe_terminate_process.h
@@ -0,0 +1,55 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_SAFE_TERMINATE_PROCESS_H_ +#define CRASHPAD_UTIL_WIN_SAFE_TERMINATE_PROCESS_H_ + +#include <windows.h> + +#include "build/build_config.h" + +namespace crashpad { + +//! \brief Calls `TerminateProcess()`. +//! +//! `TerminateProcess()` has been observed in the wild as being patched badly on +//! 32-bit x86: it’s patched with code adhering to the `cdecl` (caller clean-up) +//! convention, although it’s supposed to be `stdcall` (callee clean-up). The +//! mix-up means that neither caller nor callee perform parameter clean-up from +//! the stack, causing the stack pointer to have an unexpected value on return +//! from the patched function. This typically results in a crash shortly +//! thereafter. See <a href="https://crashpad.chromium.org/bug/179">Crashpad bug +//! 179</a>. +//! +//! On 32-bit x86, this replacement function calls `TerminateProcess()` without +//! making any assumptions about the stack pointer on its return. As such, it’s +//! compatible with the badly patched `cdecl` version as well as the native +//! `stdcall` version (and other less badly patched versions). +//! +//! Elsewhere, this function calls `TerminateProcess()` directly without any +//! additional fanfare. +//! +//! Call this function instead of `TerminateProcess()` anywhere that +//! `TerminateProcess()` would normally be called. +bool SafeTerminateProcess(HANDLE process, UINT exit_code); + +#if !defined(ARCH_CPU_X86) +inline bool SafeTerminateProcess(HANDLE process, UINT exit_code) { + return TerminateProcess(process, exit_code) != FALSE; +} +#endif // !ARCH_CPU_X86 + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SAFE_TERMINATE_PROCESS_H_
diff --git a/src/third_party/crashpad/util/win/safe_terminate_process_broken.cc b/src/third_party/crashpad/util/win/safe_terminate_process_broken.cc new file mode 100644 index 0000000..2429c72 --- /dev/null +++ b/src/third_party/crashpad/util/win/safe_terminate_process_broken.cc
@@ -0,0 +1,34 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/safe_terminate_process.h" + +#include "base/logging.h" + +#if defined(ARCH_CPU_X86) + +namespace crashpad { + +bool SafeTerminateProcess(HANDLE process, UINT exit_code) { + // Third-party software that hooks TerminateProcess() incorrectly has been + // encountered in the wild. This version of SafeTerminateProcess() lacks + // protection against that, so don't use it in production. + LOG(WARNING) + << "Don't use this! For cross builds only. See https://crbug.com/777924."; + return TerminateProcess(process, exit_code) != FALSE; +} + +} // namespace crashpad + +#endif // defined(ARCH_CPU_X86)
diff --git a/src/third_party/crashpad/util/win/safe_terminate_process_test.cc b/src/third_party/crashpad/util/win/safe_terminate_process_test.cc new file mode 100644 index 0000000..d2e4b6d --- /dev/null +++ b/src/third_party/crashpad/util/win/safe_terminate_process_test.cc
@@ -0,0 +1,186 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/safe_terminate_process.h" + +#include <string.h> + +#include <string> +#include <memory> + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/stl_util.h" +#include "build/build_config.h" +#include "gtest/gtest.h" +#include "test/errors.h" +#include "test/test_paths.h" +#include "test/win/child_launcher.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { +namespace test { +namespace { + +// Patches executable code, saving a copy of the original code so that it can be +// restored on destruction. +class ScopedExecutablePatch { + public: + ScopedExecutablePatch(void* target, const void* source, size_t size) + : original_(new uint8_t[size]), target_(target), size_(size) { + memcpy(original_.get(), target_, size_); + + ScopedVirtualProtectRWX protect_rwx(target_, size_); + memcpy(target_, source, size_); + } + + ~ScopedExecutablePatch() { + ScopedVirtualProtectRWX protect_rwx(target_, size_); + memcpy(target_, original_.get(), size_); + } + + private: + // Sets the protection on (address, size) to PAGE_EXECUTE_READWRITE by calling + // VirtualProtect(), and restores the original protection on destruction. Note + // that the region may span multiple pages, but the first page’s original + // protection will be applied to the entire region on destruction. This + // shouldn’t be a problem in practice for patching a function for this test’s + // purposes. + class ScopedVirtualProtectRWX { + public: + // If either the constructor or destructor fails, PCHECK() to terminate + // immediately, because the process will be in a weird and untrustworthy + // state, and gtest error handling isn’t worthwhile at that point. + + ScopedVirtualProtectRWX(void* address, size_t size) + : address_(address), size_(size) { + PCHECK(VirtualProtect( + address_, size_, PAGE_EXECUTE_READWRITE, &old_protect_)) + << "VirtualProtect"; + } + + ~ScopedVirtualProtectRWX() { + DWORD last_protect_; + PCHECK(VirtualProtect(address_, size_, old_protect_, &last_protect_)) + << "VirtualProtect"; + } + + private: + void* address_; + size_t size_; + DWORD old_protect_; + + DISALLOW_COPY_AND_ASSIGN(ScopedVirtualProtectRWX); + }; + + std::unique_ptr<uint8_t[]> original_; + void* target_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(ScopedExecutablePatch); +}; + +// SafeTerminateProcess is calling convention specific only for x86. +#if defined(ARCH_CPU_X86_FAMILY) +TEST(SafeTerminateProcess, PatchBadly) { + // This is a test of SafeTerminateProcess(), but it doesn’t actually terminate + // anything. Instead, it works with a process handle for the current process + // that doesn’t have PROCESS_TERMINATE access. The whole point of this test is + // to patch the real TerminateProcess() badly with a cdecl implementation to + // ensure that SafeTerminateProcess() can recover from such gross misconduct. + // The actual termination isn’t relevant to this test. + // + // Notably, don’t duplicate the process handle with PROCESS_TERMINATE access + // or with the DUPLICATE_SAME_ACCESS option. The SafeTerminateProcess() calls + // that follow operate on a duplicate of the current process’ process handle, + // and they’re supposed to fail, not terminate this process. + HANDLE process; + ASSERT_TRUE(DuplicateHandle(GetCurrentProcess(), + GetCurrentProcess(), + GetCurrentProcess(), + &process, + PROCESS_QUERY_INFORMATION, + false, + 0)) + << ErrorMessage("DuplicateHandle"); + ScopedKernelHANDLE process_owner(process); + + // Make sure that TerminateProcess() works as a baseline. + SetLastError(ERROR_SUCCESS); + EXPECT_FALSE(TerminateProcess(process, 0)); + EXPECT_EQ(GetLastError(), static_cast<DWORD>(ERROR_ACCESS_DENIED)); + + // Make sure that SafeTerminateProcess() works, calling through to + // TerminateProcess() properly. + SetLastError(ERROR_SUCCESS); + EXPECT_FALSE(SafeTerminateProcess(process, 0)); + EXPECT_EQ(GetLastError(), static_cast<DWORD>(ERROR_ACCESS_DENIED)); + + { + // Patch TerminateProcess() badly. This turns it into a no-op that returns 0 + // without cleaning up arguments from the stack, as a stdcall function is + // expected to do. + // + // This simulates the unexpected cdecl-patched TerminateProcess() as seen at + // https://crashpad.chromium.org/bug/179. In reality, this only affects + // 32-bit x86, as there’s no calling convention confusion on x86_64. It + // doesn’t hurt to run this test in the 64-bit environment, though. + static constexpr uint8_t patch[] = { +#if defined(ARCH_CPU_X86) + 0x31, 0xc0, // xor eax, eax +#elif defined(ARCH_CPU_X86_64) + 0x48, 0x31, 0xc0, // xor rax, rax +#else +#error Port +#endif + 0xc3, // ret + }; + + void* target = reinterpret_cast<void*>(TerminateProcess); + ScopedExecutablePatch executable_patch(target, patch, base::size(patch)); + + // Make sure that SafeTerminateProcess() can be called. Since it’s been + // patched with a no-op stub, GetLastError() shouldn’t be modified. + SetLastError(ERROR_SUCCESS); + EXPECT_FALSE(SafeTerminateProcess(process, 0)); + EXPECT_EQ(GetLastError(), static_cast<DWORD>(ERROR_SUCCESS)); + } + + // Now that the real TerminateProcess() has been restored, verify that it + // still works properly. + SetLastError(ERROR_SUCCESS); + EXPECT_FALSE(SafeTerminateProcess(process, 0)); + EXPECT_EQ(GetLastError(), static_cast<DWORD>(ERROR_ACCESS_DENIED)); +} +#endif // ARCH_CPU_X86_FAMILY + +TEST(SafeTerminateProcess, TerminateChild) { + base::FilePath child_executable = + TestPaths::BuildArtifact(L"util", + L"safe_terminate_process_test_child", + TestPaths::FileType::kExecutable); + ChildLauncher child(child_executable, L""); + ASSERT_NO_FATAL_FAILURE(child.Start()); + + constexpr DWORD kExitCode = 0x51ee9d1e; // Sort of like “sleep and die.” + + ASSERT_TRUE(SafeTerminateProcess(child.process_handle(), kExitCode)) + << ErrorMessage("TerminateProcess"); + EXPECT_EQ(child.WaitForExit(), kExitCode); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/safe_terminate_process_test_child.cc b/src/third_party/crashpad/util/win/safe_terminate_process_test_child.cc new file mode 100644 index 0000000..605778b --- /dev/null +++ b/src/third_party/crashpad/util/win/safe_terminate_process_test_child.cc
@@ -0,0 +1,22 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <stdlib.h> +#include <wchar.h> +#include <windows.h> + +int wmain(int argc, wchar_t** argv) { + Sleep(INFINITE); + return EXIT_FAILURE; +}
diff --git a/src/third_party/crashpad/util/win/scoped_handle.cc b/src/third_party/crashpad/util/win/scoped_handle.cc new file mode 100644 index 0000000..5e79e57 --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_handle.cc
@@ -0,0 +1,36 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/scoped_handle.h" + +#include "base/logging.h" +#include "util/file/file_io.h" + +namespace crashpad { +namespace internal { + +void ScopedFileHANDLECloseTraits::Free(HANDLE handle) { + CheckedCloseFile(handle); +} + +void ScopedKernelHANDLECloseTraits::Free(HANDLE handle) { + PCHECK(CloseHandle(handle)) << "CloseHandle"; +} + +void ScopedSearchHANDLECloseTraits::Free(HANDLE handle) { + PCHECK(FindClose(handle)) << "FindClose"; +} + +} // namespace internal +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/scoped_handle.h b/src/third_party/crashpad/util/win/scoped_handle.h new file mode 100644 index 0000000..a5893b8 --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_handle.h
@@ -0,0 +1,52 @@ +// Copyright 2014 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_SCOPED_HANDLE_H_ +#define CRASHPAD_UTIL_WIN_SCOPED_HANDLE_H_ + +#include <windows.h> + +#include "base/scoped_generic.h" + +namespace crashpad { + +namespace internal { + +struct ScopedFileHANDLECloseTraits { + static HANDLE InvalidValue() { return INVALID_HANDLE_VALUE; } + static void Free(HANDLE handle); +}; + +struct ScopedKernelHANDLECloseTraits { + static HANDLE InvalidValue() { return nullptr; } + static void Free(HANDLE handle); +}; + +struct ScopedSearchHANDLECloseTraits { + static HANDLE InvalidValue() { return INVALID_HANDLE_VALUE; } + static void Free(HANDLE handle); +}; + +} // namespace internal + +using ScopedFileHANDLE = + base::ScopedGeneric<HANDLE, internal::ScopedFileHANDLECloseTraits>; +using ScopedKernelHANDLE = + base::ScopedGeneric<HANDLE, internal::ScopedKernelHANDLECloseTraits>; +using ScopedSearchHANDLE = + base::ScopedGeneric<HANDLE, internal::ScopedSearchHANDLECloseTraits>; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SCOPED_HANDLE_H_
diff --git a/src/third_party/crashpad/util/win/scoped_local_alloc.cc b/src/third_party/crashpad/util/win/scoped_local_alloc.cc new file mode 100644 index 0000000..75838bc --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_local_alloc.cc
@@ -0,0 +1,28 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/scoped_local_alloc.h" + +#include "base/logging.h" + +namespace crashpad { +namespace internal { + +// static +void LocalAllocTraits::Free(HLOCAL memory) { + PLOG_IF(ERROR, LocalFree(memory) != nullptr) << "LocalFree"; +} + +} // namespace internal +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/scoped_local_alloc.h b/src/third_party/crashpad/util/win/scoped_local_alloc.h new file mode 100644 index 0000000..b460785 --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_local_alloc.h
@@ -0,0 +1,38 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_SCOPED_LOCAL_ALLOC_H_ +#define CRASHPAD_UTIL_WIN_SCOPED_LOCAL_ALLOC_H_ + +#include <windows.h> + +#include "base/scoped_generic.h" + +namespace crashpad { + +namespace internal { + +struct LocalAllocTraits { + static HLOCAL InvalidValue() { return nullptr; } + static void Free(HLOCAL mem); +}; + +} // namespace internal + +using ScopedLocalAlloc = + base::ScopedGeneric<HLOCAL, internal::LocalAllocTraits>; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SCOPED_LOCAL_ALLOC_H_
diff --git a/src/third_party/crashpad/util/win/scoped_process_suspend.cc b/src/third_party/crashpad/util/win/scoped_process_suspend.cc new file mode 100644 index 0000000..721eb7f --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_process_suspend.cc
@@ -0,0 +1,49 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/scoped_process_suspend.h" + +#include <stddef.h> +#include <winternl.h> + +#include "util/win/nt_internals.h" +#include "util/win/ntstatus_logging.h" + +namespace crashpad { + +ScopedProcessSuspend::ScopedProcessSuspend(HANDLE process) { + NTSTATUS status = NtSuspendProcess(process); + if (NT_SUCCESS(status)) { + process_ = process; + } else { + process_ = nullptr; + NTSTATUS_LOG(ERROR, status) << "NtSuspendProcess"; + } +} + +ScopedProcessSuspend::~ScopedProcessSuspend() { + if (process_) { + NTSTATUS status = NtResumeProcess(process_); + if (!NT_SUCCESS(status) && + (!tolerate_termination_ || status != STATUS_PROCESS_IS_TERMINATING)) { + NTSTATUS_LOG(ERROR, status) << "NtResumeProcess"; + } + } +} + +void ScopedProcessSuspend::TolerateTermination() { + tolerate_termination_ = true; +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/scoped_process_suspend.h b/src/third_party/crashpad/util/win/scoped_process_suspend.h new file mode 100644 index 0000000..913886e --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_process_suspend.h
@@ -0,0 +1,56 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_SCOPED_PROCESS_SUSPEND_H_ +#define CRASHPAD_UTIL_WIN_SCOPED_PROCESS_SUSPEND_H_ + +#include <windows.h> + +#include "base/macros.h" + +namespace crashpad { + +//! \brief Manages the suspension of another process. +//! +//! While an object of this class exists, the other process will be suspended. +//! Once the object is destroyed, the other process will become eligible for +//! resumption. +//! +//! If this process crashes while this object exists, there is no guarantee that +//! the other process will be resumed. +class ScopedProcessSuspend { + public: + //! Does not take ownership of \a process. + explicit ScopedProcessSuspend(HANDLE process); + ~ScopedProcessSuspend(); + + //! \brief Informs the object that the suspended process may be terminating, + //! and that this should not be treated as an error. + //! + //! Normally, attempting to resume a terminating process during destruction + //! results in an error message being logged for + //! `STATUS_PROCESS_IS_TERMINATING`. When it is known that a process may be + //! terminating, this method may be called to suppress that error message. + void TolerateTermination(); + + private: + HANDLE process_; + bool tolerate_termination_ = false; + + DISALLOW_COPY_AND_ASSIGN(ScopedProcessSuspend); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SCOPED_PROCESS_SUSPEND_H_
diff --git a/src/third_party/crashpad/util/win/scoped_process_suspend_test.cc b/src/third_party/crashpad/util/win/scoped_process_suspend_test.cc new file mode 100644 index 0000000..2d0f5a0 --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_process_suspend_test.cc
@@ -0,0 +1,119 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/scoped_process_suspend.h" + +#include <stddef.h> +#include <tlhelp32.h> + +#include <algorithm> +#include <vector> + +#include "gtest/gtest.h" +#include "test/errors.h" +#include "test/win/win_child_process.h" +#include "util/win/xp_compat.h" + +namespace crashpad { +namespace test { +namespace { + +// There is no per-process suspend count on Windows, only a per-thread suspend +// count. NtSuspendProcess just suspends all threads of a given process. So, +// verify that all thread's suspend counts match the desired suspend count. +bool SuspendCountMatches(HANDLE process, DWORD desired_suspend_count) { + DWORD process_id = GetProcessId(process); + + ScopedKernelHANDLE snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0)); + if (!snapshot.is_valid()) { + ADD_FAILURE() << ErrorMessage("CreateToolhelp32Snapshot"); + return false; + } + + THREADENTRY32 te; + te.dwSize = sizeof(te); + + BOOL ret = Thread32First(snapshot.get(), &te); + if (!ret) { + ADD_FAILURE() << ErrorMessage("Thread32First"); + return false; + } + do { + if (te.dwSize >= offsetof(THREADENTRY32, th32OwnerProcessID) + + sizeof(te.th32OwnerProcessID) && + te.th32OwnerProcessID == process_id) { + ScopedKernelHANDLE thread( + OpenThread(kXPThreadAllAccess, false, te.th32ThreadID)); + EXPECT_TRUE(thread.is_valid()) << ErrorMessage("OpenThread"); + DWORD result = SuspendThread(thread.get()); + EXPECT_NE(result, static_cast<DWORD>(-1)) + << ErrorMessage("SuspendThread"); + if (result != static_cast<DWORD>(-1)) { + EXPECT_NE(ResumeThread(thread.get()), static_cast<DWORD>(-1)) + << ErrorMessage("ResumeThread"); + } + if (result != desired_suspend_count) + return false; + } + te.dwSize = sizeof(te); + } while (Thread32Next(snapshot.get(), &te)); + + return true; +} + +class ScopedProcessSuspendTest final : public WinChildProcess { + public: + ScopedProcessSuspendTest() : WinChildProcess() {} + ~ScopedProcessSuspendTest() {} + + private: + int Run() override { + char c; + // Wait for notification from parent. + EXPECT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c))); + EXPECT_EQ(c, ' '); + return EXIT_SUCCESS; + } + + DISALLOW_COPY_AND_ASSIGN(ScopedProcessSuspendTest); +}; + +TEST(ScopedProcessSuspend, ScopedProcessSuspend) { + WinChildProcess::EntryPoint<ScopedProcessSuspendTest>(); + std::unique_ptr<WinChildProcess::Handles> handles = WinChildProcess::Launch(); + + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 0)); + + { + ScopedProcessSuspend suspend0(handles->process.get()); + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 1)); + + { + ScopedProcessSuspend suspend1(handles->process.get()); + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 2)); + } + + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 1)); + } + + EXPECT_TRUE(SuspendCountMatches(handles->process.get(), 0)); + + // Tell the child it's OK to terminate. + char c = ' '; + EXPECT_TRUE(WriteFile(handles->write.get(), &c, sizeof(c))); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/scoped_registry_key.h b/src/third_party/crashpad/util/win/scoped_registry_key.h new file mode 100644 index 0000000..4393dc2 --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_registry_key.h
@@ -0,0 +1,34 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_ +#define CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_ + +#include <windows.h> + +#include "base/scoped_generic.h" + +namespace crashpad { + +struct ScopedRegistryKeyCloseTraits { + static HKEY InvalidValue() { return nullptr; } + static void Free(HKEY key) { RegCloseKey(key); } +}; + +using ScopedRegistryKey = + base::ScopedGeneric<HKEY, ScopedRegistryKeyCloseTraits>; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SCOPED_REGISTRY_KEY_H_ \ No newline at end of file
diff --git a/src/third_party/crashpad/util/win/scoped_set_event.cc b/src/third_party/crashpad/util/win/scoped_set_event.cc new file mode 100644 index 0000000..7fb1647 --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_set_event.cc
@@ -0,0 +1,40 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/scoped_set_event.h" + +#include "base/logging.h" + +namespace crashpad { + +ScopedSetEvent::ScopedSetEvent(HANDLE event) : event_(event) { + DCHECK(event_); +} + +ScopedSetEvent::~ScopedSetEvent() { + if (event_) { + Set(); + } +} + +bool ScopedSetEvent::Set() { + bool rv = !!SetEvent(event_); + if (!rv) { + PLOG(ERROR) << "SetEvent"; + } + event_ = nullptr; + return rv; +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/scoped_set_event.h b/src/third_party/crashpad/util/win/scoped_set_event.h new file mode 100644 index 0000000..82a1b31 --- /dev/null +++ b/src/third_party/crashpad/util/win/scoped_set_event.h
@@ -0,0 +1,48 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_SCOPED_SET_EVENT_H_ +#define CRASHPAD_UTIL_WIN_SCOPED_SET_EVENT_H_ + +#include <windows.h> + +#include "base/macros.h" + +namespace crashpad { + +//! \brief Calls `SetEvent()` on destruction at latest. +//! +//! Does not assume ownership of the event handle. Use ScopedKernelHANDLE for +//! ownership. +class ScopedSetEvent { + public: + explicit ScopedSetEvent(HANDLE event); + ~ScopedSetEvent(); + + //! \brief Calls `SetEvent()` immediately. + //! + //! `SetEvent()` will not be called on destruction. + //! + //! \return `true` on success, `false` on failure with a message logged. + bool Set(); + + private: + HANDLE event_; // weak + + DISALLOW_COPY_AND_ASSIGN(ScopedSetEvent); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SCOPED_SET_EVENT_H_
diff --git a/src/third_party/crashpad/util/win/session_end_watcher.cc b/src/third_party/crashpad/util/win/session_end_watcher.cc new file mode 100644 index 0000000..1d47089 --- /dev/null +++ b/src/third_party/crashpad/util/win/session_end_watcher.cc
@@ -0,0 +1,229 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/session_end_watcher.h" + +#include "base/logging.h" +#include "base/scoped_generic.h" +#include "util/win/scoped_set_event.h" + +extern "C" { +extern IMAGE_DOS_HEADER __ImageBase; +} // extern "C" + +namespace crashpad { + +namespace { + +// ScopedWindowClass and ScopedWindow operate on ATOM* and HWND*, respectively, +// instead of ATOM and HWND, so that the actual storage can exist as a local +// variable or a member variable, and the scoper can be responsible for +// releasing things only if the actual storage hasn’t been released and zeroed +// already by something else. +struct ScopedWindowClassTraits { + static ATOM* InvalidValue() { return nullptr; } + static void Free(ATOM* window_class) { + if (*window_class) { + if (!UnregisterClass(MAKEINTATOM(*window_class), 0)) { + PLOG(ERROR) << "UnregisterClass"; + } else { + *window_class = 0; + } + } + } +}; +using ScopedWindowClass = base::ScopedGeneric<ATOM*, ScopedWindowClassTraits>; + +struct ScopedWindowTraits { + static HWND* InvalidValue() { return nullptr; } + static void Free(HWND* window) { + if (*window) { + if (!DestroyWindow(*window)) { + PLOG(ERROR) << "DestroyWindow"; + } else { + *window = nullptr; + } + } + } +}; +using ScopedWindow = base::ScopedGeneric<HWND*, ScopedWindowTraits>; + +// GetWindowLongPtr()’s return value doesn’t unambiguously indicate whether it +// was successful, because 0 could either represent successful retrieval of the +// value 0, or failure. This wrapper is more convenient to use. +bool GetWindowLongPtrAndSuccess(HWND window, int index, LONG_PTR* value) { + SetLastError(ERROR_SUCCESS); + *value = GetWindowLongPtr(window, index); + return *value || GetLastError() == ERROR_SUCCESS; +} + +// SetWindowLongPtr() has the same problem as GetWindowLongPtr(). Use this +// wrapper instead. +bool SetWindowLongPtrAndGetSuccess(HWND window, int index, LONG_PTR value) { + SetLastError(ERROR_SUCCESS); + LONG_PTR previous = SetWindowLongPtr(window, index, value); + return previous || GetLastError() == ERROR_SUCCESS; +} + +} // namespace + +SessionEndWatcher::SessionEndWatcher() + : Thread(), + window_(nullptr), + started_(nullptr), + stopped_(nullptr) { + // Set bManualReset for these events so that WaitForStart() and WaitForStop() + // can be called multiple times. + + started_.reset(CreateEvent(nullptr, true, false, nullptr)); + PLOG_IF(ERROR, !started_.get()) << "CreateEvent"; + + stopped_.reset(CreateEvent(nullptr, true, false, nullptr)); + PLOG_IF(ERROR, !stopped_.get()) << "CreateEvent"; + + Start(); +} + +SessionEndWatcher::~SessionEndWatcher() { + // Tear everything down by posting a WM_CLOSE to the window. This obviously + // can’t work until the window has been created, and that happens on a + // different thread, so wait for the start event to be signaled first. + WaitForStart(); + if (window_) { + if (!PostMessage(window_, WM_CLOSE, 0, 0)) { + PLOG(ERROR) << "PostMessage"; + } + } + + Join(); + DCHECK(!window_); +} + +void SessionEndWatcher::WaitForStart() { + if (WaitForSingleObject(started_.get(), INFINITE) != WAIT_OBJECT_0) { + PLOG(ERROR) << "WaitForSingleObject"; + } +} + +void SessionEndWatcher::WaitForStop() { + if (WaitForSingleObject(stopped_.get(), INFINITE) != WAIT_OBJECT_0) { + PLOG(ERROR) << "WaitForSingleObject"; + } +} + +void SessionEndWatcher::ThreadMain() { + ATOM atom = 0; + ScopedWindowClass window_class(&atom); + ScopedWindow window(&window_); + + ScopedSetEvent call_set_stop(stopped_.get()); + + { + ScopedSetEvent call_set_start(started_.get()); + + WNDCLASS wndclass = {}; + wndclass.lpfnWndProc = WindowProc; + wndclass.hInstance = reinterpret_cast<HMODULE>(&__ImageBase); + wndclass.lpszClassName = L"crashpad_SessionEndWatcher"; + atom = RegisterClass(&wndclass); + if (!atom) { + PLOG(ERROR) << "RegisterClass"; + return; + } + + window_ = CreateWindow(MAKEINTATOM(atom), // lpClassName + nullptr, // lpWindowName + 0, // dwStyle + 0, // x + 0, // y + 0, // nWidth + 0, // nHeight + nullptr, // hWndParent + nullptr, // hMenu + nullptr, // hInstance + this); // lpParam + if (!window_) { + PLOG(ERROR) << "CreateWindow"; + return; + } + } + + MSG message; + BOOL rv = 0; + while (window_ && (rv = GetMessage(&message, window_, 0, 0)) > 0) { + TranslateMessage(&message); + DispatchMessage(&message); + } + if (window_ && rv == -1) { + PLOG(ERROR) << "GetMessage"; + return; + } +} + +// static +LRESULT CALLBACK SessionEndWatcher::WindowProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param) { + // Figure out which object this is. A pointer to it is stuffed into the last + // parameter of CreateWindow(), which shows up as CREATESTRUCT::lpCreateParams + // in a WM_CREATE message. That should be processed before any of the other + // messages of interest to this function. Once the object is known, save a + // pointer to it in the GWLP_USERDATA slot for later retrieval when processing + // other messages. + SessionEndWatcher* self; + if (!GetWindowLongPtrAndSuccess( + window, GWLP_USERDATA, reinterpret_cast<LONG_PTR*>(&self))) { + PLOG(ERROR) << "GetWindowLongPtr"; + } + if (!self && message == WM_CREATE) { + CREATESTRUCT* create = reinterpret_cast<CREATESTRUCT*>(l_param); + self = reinterpret_cast<SessionEndWatcher*>(create->lpCreateParams); + if (!SetWindowLongPtrAndGetSuccess( + window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(self))) { + PLOG(ERROR) << "SetWindowLongPtr"; + } + } + + if (self) { + if (message == WM_ENDSESSION) { + // If w_param is false, this WM_ENDSESSION message cancels a previous + // WM_QUERYENDSESSION. + if (w_param) { + self->SessionEnding(); + + // If the session is ending, post a close message which will kick off + // window destruction and cause the message loop thread to terminate. + if (!PostMessage(self->window_, WM_CLOSE, 0, 0)) { + PLOG(ERROR) << "PostMessage"; + } + } + } else if (message == WM_DESTROY) { + // The window is being destroyed. Clear GWLP_USERDATA so that |self| won’t + // be found during a subsequent call into this function for this window. + // Clear self->window_ too, because it refers to an object that soon won’t + // exist. That signals the message loop to stop processing messages. + if (!SetWindowLongPtrAndGetSuccess(window, GWLP_USERDATA, 0)) { + PLOG(ERROR) << "SetWindowLongPtr"; + } + self->window_ = nullptr; + } + } + + // If the message is WM_CLOSE, DefWindowProc() will call DestroyWindow(), and + // this function will be called again with a WM_DESTROY message. + return DefWindowProc(window, message, w_param, l_param); +} + +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/session_end_watcher.h b/src/third_party/crashpad/util/win/session_end_watcher.h new file mode 100644 index 0000000..b23d391 --- /dev/null +++ b/src/third_party/crashpad/util/win/session_end_watcher.h
@@ -0,0 +1,79 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_SESSION_END_WATCHER_H_ +#define CRASHPAD_UTIL_WIN_SESSION_END_WATCHER_H_ + +#include <windows.h> + +#include "base/macros.h" +#include "util/thread/thread.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { + +//! \brief Creates a hidden window and waits for a `WM_ENDSESSION` message, +//! indicating that the session is ending and the application should +//! terminate. +//! +//! A dedicated thread will be created to run the `GetMessage()`-based message +//! loop required to monitor for this message. +//! +//! Users should subclass this class and receive notifications by implementing +//! the SessionEndWatcherEvent() method. +class SessionEndWatcher : public Thread { + public: + SessionEndWatcher(); + + //! \note The destructor waits for the thread that runs the message loop to + //! terminate. + ~SessionEndWatcher() override; + + protected: + // Exposed for testing. + HWND GetWindow() const { return window_; } + + // Exposed for testing. Blocks until window_ has been created. May be called + // multiple times if necessary. + void WaitForStart(); + + // Exposed for testing. Blocks until the message loop ends. May be called + // multiple times if necessary. + void WaitForStop(); + + private: + // Thread: + void ThreadMain() override; + + static LRESULT CALLBACK WindowProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param); + + //! \brief A `WM_ENDSESSION` message was received and it indicates that the + //! user session will be ending imminently. + //! + //! This method is called on the thread that runs the message loop. + virtual void SessionEnding() = 0; + + HWND window_; // Conceptually strong, but ownership managed in ThreadMain() + ScopedKernelHANDLE started_; + ScopedKernelHANDLE stopped_; + + DISALLOW_COPY_AND_ASSIGN(SessionEndWatcher); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_SESSION_END_WATCHER_H_
diff --git a/src/third_party/crashpad/util/win/session_end_watcher_test.cc b/src/third_party/crashpad/util/win/session_end_watcher_test.cc new file mode 100644 index 0000000..692d76e --- /dev/null +++ b/src/third_party/crashpad/util/win/session_end_watcher_test.cc
@@ -0,0 +1,62 @@ +// Copyright 2017 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/win/session_end_watcher.h" + +#include "gtest/gtest.h" +#include "test/errors.h" + +namespace crashpad { +namespace test { +namespace { + +class SessionEndWatcherTest final : public SessionEndWatcher { + public: + SessionEndWatcherTest() : SessionEndWatcher(), called_(false) {} + + ~SessionEndWatcherTest() override {} + + void Run() { + WaitForStart(); + + HWND window = GetWindow(); + ASSERT_TRUE(window); + EXPECT_TRUE(PostMessage(window, WM_ENDSESSION, 1, 0)); + + WaitForStop(); + + EXPECT_TRUE(called_); + } + + private: + // SessionEndWatcher: + void SessionEnding() override { called_ = true; } + + bool called_; + + DISALLOW_COPY_AND_ASSIGN(SessionEndWatcherTest); +}; + +TEST(SessionEndWatcher, SessionEndWatcher) { + SessionEndWatcherTest test; + test.Run(); +} + +TEST(SessionEndWatcher, DoNothing) { + SessionEndWatcherTest test; +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/src/third_party/crashpad/util/win/termination_codes.h b/src/third_party/crashpad/util/win/termination_codes.h new file mode 100644 index 0000000..ce7fdba --- /dev/null +++ b/src/third_party/crashpad/util/win/termination_codes.h
@@ -0,0 +1,37 @@ +// Copyright 2016 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_TERMINATION_CODES_H_ +#define CRASHPAD_UTIL_WIN_TERMINATION_CODES_H_ + +namespace crashpad { + +//! \brief Crashpad-specific codes that are used as arguments to +//! SafeTerminateProcess() or `TerminateProcess()` in unusual circumstances. +enum TerminationCodes : unsigned int { + //! \brief The crash handler did not respond, and the client self-terminated. + kTerminationCodeCrashNoDump = 0xffff7001, + + //! \brief The initial process snapshot failed, so the correct client + //! termination code could not be retrieved. + kTerminationCodeSnapshotFailed = 0xffff7002, + + //! \brief A dump was requested for a client that was never registered with + //! the crash handler. + kTerminationCodeNotConnectedToHandler = 0xffff7003, +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_TERMINATION_CODES_H_
diff --git a/src/third_party/crashpad/util/win/xp_compat.h b/src/third_party/crashpad/util/win/xp_compat.h new file mode 100644 index 0000000..1d4d4a0 --- /dev/null +++ b/src/third_party/crashpad/util/win/xp_compat.h
@@ -0,0 +1,40 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_UTIL_WIN_XP_COMPAT_H_ +#define CRASHPAD_UTIL_WIN_XP_COMPAT_H_ + +#include <windows.h> + +namespace crashpad { + +enum { + //! \brief This is the XP-suitable value of `PROCESS_ALL_ACCESS`. + //! + //! Requesting `PROCESS_ALL_ACCESS` with the value defined when building + //! against a Vista+ SDK results in `ERROR_ACCESS_DENIED` when running on XP. + //! See https://msdn.microsoft.com/library/ms684880.aspx. + kXPProcessAllAccess = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF, + + //! \brief This is the XP-suitable value of `THREAD_ALL_ACCESS`. + //! + //! Requesting `THREAD_ALL_ACCESS` with the value defined when building + //! against a Vista+ SDK results in `ERROR_ACCESS_DENIED` when running on XP. + //! See https://msdn.microsoft.com/library/ms686769.aspx. + kXPThreadAllAccess = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3FF, +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_XP_COMPAT_H_