diff --git a/build/run_tests.py b/build/run_tests.py index 1f4a089a..9075cdf1 100755 --- a/build/run_tests.py +++ b/build/run_tests.py @@ -15,6 +15,7 @@ # limitations under the License. import os +import platform import subprocess import sys @@ -50,6 +51,10 @@ def main(args): 'crashpad_test_test', 'crashpad_util_test', ] + if platform.system() == 'Windows': + tests += [ + 'crashpad_handler_test', + ] for test in tests: print '-' * 80 print test diff --git a/client/client.gyp b/client/client.gyp index a34d7a90..db695a1f 100644 --- a/client/client.gyp +++ b/client/client.gyp @@ -40,6 +40,7 @@ 'crashpad_client_win.cc', 'crashpad_info.cc', 'crashpad_info.h', + 'registration_protocol_win.h', 'settings.cc', 'settings.h', 'simple_string_dictionary.cc', diff --git a/client/registration_protocol_win.h b/client/registration_protocol_win.h new file mode 100644 index 00000000..75a58051 --- /dev/null +++ b/client/registration_protocol_win.h @@ -0,0 +1,55 @@ +// 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_CLIENT_REGISTRATION_PROTOCOL_WIN_H_ +#define CRASHPAD_CLIENT_REGISTRATION_PROTOCOL_WIN_H_ + +#include +#include + +#include "util/win/address_types.h" + +namespace crashpad { + +#pragma pack(push, 1) +//! \brief A client registration request. +struct RegistrationRequest { + //! \brief The PID of the client process. + DWORD client_process_id; + //! \brief The address, in the client process address space, of a CrashpadInfo + //! structure. + crashpad::WinVMAddress crashpad_info_address; +}; +#pragma pack(pop) + +#pragma pack(push, 1) +//! \brief A client registration response. See +//! https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203 for +//! details on communicating handle values between 32-bit and 64-bit +//! processes. +struct RegistrationResponse { + //! \brief An event `HANDLE`, valid in the client process, that should be + //! signaled to request a crash report. 64-bit clients should convert the + //! value to a `HANDLE` using sign-extension. + uint32_t request_report_event; + //! \brief An event `HANDLE`, valid in the client process, that will be + //! signaled when the requested crash report is complete. 64-bit clients + //! should convert the value to a `HANDLE` using sign-extension. + uint32_t report_complete_event; +}; +#pragma pack(pop) + +} // namespace crashpad + +#endif // CRASHPAD_CLIENT_REGISTRATION_PROTOCOL_WIN_H_ diff --git a/crashpad.gyp b/crashpad.gyp index fd2a2691..85f98ee0 100644 --- a/crashpad.gyp +++ b/crashpad.gyp @@ -23,6 +23,7 @@ 'client/client_test.gyp:*', 'compat/compat.gyp:*', 'handler/handler.gyp:*', + 'handler/handler_test.gyp:*', 'minidump/minidump.gyp:*', 'minidump/minidump_test.gyp:*', 'snapshot/snapshot.gyp:*', diff --git a/handler/handler.gyp b/handler/handler.gyp index 082eebea..a78dc8d2 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -68,6 +68,28 @@ ], }, ], + },], + ['OS=="win"', { + 'targets': [ + { + 'target_name': 'crashpad_handler', + 'type': 'static_library', + 'dependencies': [ + '../client/client.gyp:crashpad_client', + '../third_party/mini_chromium/mini_chromium.gyp:base', + '../util/util.gyp:crashpad_util', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'win/registration_pipe_state.cc', + 'win/registration_pipe_state.h', + 'win/registration_server.cc', + 'win/registration_server.h', + ], + }, + ], }, { 'targets': [], }], diff --git a/handler/handler_test.gyp b/handler/handler_test.gyp new file mode 100644 index 00000000..8d7405c9 --- /dev/null +++ b/handler/handler_test.gyp @@ -0,0 +1,48 @@ +# 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. + +{ + 'includes': [ + '../build/crashpad.gypi', + ], + 'targets': [], + 'conditions': [ + ['OS=="win"', { + 'targets': [ + { + 'target_name': 'crashpad_handler_test', + 'type': 'executable', + 'dependencies': [ + 'handler.gyp:crashpad_handler', + '../compat/compat.gyp:crashpad_compat', + '../test/test.gyp:crashpad_test', + '../third_party/gtest/gtest.gyp:gtest', + '../third_party/gtest/gtest.gyp:gtest_main', + '../third_party/mini_chromium/mini_chromium.gyp:base', + '../util/util.gyp:crashpad_util', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'win/registration_pipe_state_test.cc', + 'win/registration_server_test.cc', + 'win/registration_test_base.cc', + 'win/registration_test_base.h', + ], + }, + ], + }], + ], +} diff --git a/handler/win/registration_pipe_state.cc b/handler/win/registration_pipe_state.cc new file mode 100644 index 00000000..08d265f1 --- /dev/null +++ b/handler/win/registration_pipe_state.cc @@ -0,0 +1,289 @@ +// 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 "handler/win/registration_pipe_state.h" + +#include + +#include + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "util/stdlib/pointer_container.h" + +namespace crashpad { + +RegistrationPipeState::RegistrationPipeState( + ScopedFileHANDLE pipe, + RegistrationServer::Delegate* delegate) + : request_(), + response_(), + completion_handler_(nullptr), + overlapped_(), + event_(), + pipe_(pipe.Pass()), + waiting_for_close_(false), + delegate_(delegate), + get_named_pipe_client_process_id_proc_(nullptr) { + HMODULE kernel_dll = GetModuleHandle(L"kernel32.dll"); + if (kernel_dll) { + get_named_pipe_client_process_id_proc_ = + reinterpret_cast( + GetProcAddress(kernel_dll, "GetNamedPipeClientProcessId")); + } +} + +RegistrationPipeState::~RegistrationPipeState() { +} + +bool RegistrationPipeState::Initialize() { + DCHECK(!event_.is_valid()); + DCHECK(pipe_.is_valid()); + + event_.reset(CreateEvent(nullptr, true, false, nullptr)); + + if (!event_.is_valid()) { + PLOG(ERROR) << "CreateEvent"; + } else { + overlapped_.hEvent = event_.get(); + if (IssueConnect()) + return true; + } + + overlapped_.hEvent = nullptr; + event_.reset(); + pipe_.reset(); + completion_handler_ = nullptr; + + return false; +} + +void RegistrationPipeState::Stop() { + DCHECK(pipe_.is_valid()); + if (!CancelIo(pipe_.get())) + PLOG(FATAL) << "CancelIo"; +} + +bool RegistrationPipeState::OnCompletion() { + AsyncCompletionHandler completion_handler = completion_handler_; + completion_handler_ = nullptr; + + DWORD bytes_transferred = 0; + BOOL success = GetOverlappedResult(pipe_.get(), + &overlapped_, + &bytes_transferred, + false); // Do not wait. + if (!success) { + // ERROR_BROKEN_PIPE is expected when we are waiting for the client to close + // the pipe (signaling that they are done reading the response). + if (!waiting_for_close_ || GetLastError() != ERROR_BROKEN_PIPE) + PLOG(ERROR) << "GetOverlappedResult"; + } + + bool still_running = false; + if (!ResetEvent(event_.get())) { + PLOG(ERROR) << "ResetEvent"; + } else if (!completion_handler) { + NOTREACHED(); + still_running = ResetConnection(); + } else if (!success) { + still_running = ResetConnection(); + } else { + still_running = (this->*completion_handler)(bytes_transferred); + } + + if (!still_running) { + overlapped_.hEvent = nullptr; + event_.reset(); + pipe_.reset(); + completion_handler_ = nullptr; + } else { + DCHECK(completion_handler_); + } + + return still_running; +} + +bool RegistrationPipeState::OnConnectComplete(DWORD /* bytes_transferred */) { + return IssueRead(); +} + +bool RegistrationPipeState::OnReadComplete(DWORD bytes_transferred) { + if (bytes_transferred != sizeof(request_)) { + LOG(ERROR) << "Invalid message size: " << bytes_transferred; + return ResetConnection(); + } else { + return HandleRequest(); + } +} + +bool RegistrationPipeState::OnWriteComplete(DWORD bytes_transferred) { + if (bytes_transferred != sizeof(response_)) { + LOG(ERROR) << "Incomplete write operation. Bytes written: " + << bytes_transferred; + } + + return IssueWaitForClientClose(); +} + +bool RegistrationPipeState::OnWaitForClientCloseComplete( + DWORD bytes_transferred) { + LOG(ERROR) << "Unexpected extra data (" << bytes_transferred + << " bytes) received from client."; + return ResetConnection(); +} + +bool RegistrationPipeState::IssueConnect() { + if (ConnectNamedPipe(pipe_.get(), &overlapped_)) { + return OnConnectComplete(0); // bytes_transferred (ignored) + } else { + DWORD result = GetLastError(); + if (result == ERROR_PIPE_CONNECTED) { + return OnConnectComplete(0); // bytes_transferred (ignored) + } else if (result == ERROR_IO_PENDING) { + completion_handler_ = &RegistrationPipeState::OnConnectComplete; + return true; + } else { + PLOG(ERROR) << "ConnectNamedPipe"; + return false; + } + } +} + +bool RegistrationPipeState::IssueRead() { + DWORD bytes_read = 0; + if (ReadFile(pipe_.get(), + &request_, + sizeof(request_), + &bytes_read, + &overlapped_)) { + return OnReadComplete(bytes_read); + } else if (GetLastError() == ERROR_IO_PENDING) { + completion_handler_ = &RegistrationPipeState::OnReadComplete; + return true; + } else { + PLOG(ERROR) << "ReadFile"; + return ResetConnection(); + } +} + +bool RegistrationPipeState::IssueWrite() { + DWORD bytes_written = 0; + if (WriteFile(pipe_.get(), + &response_, + sizeof(response_), + &bytes_written, + &overlapped_)) { + return OnWriteComplete(bytes_written); + } else if (GetLastError() == ERROR_IO_PENDING) { + completion_handler_ = &RegistrationPipeState::OnWriteComplete; + return true; + } else { + PLOG(ERROR) << "WriteFile"; + return ResetConnection(); + } +} + +bool RegistrationPipeState::IssueWaitForClientClose() { + // If we invoke DisconnectNamedPipe before the client has read the response + // the response will never be delivered. Therefore we issue an extra ReadFile + // operation after writing the response. No data is expected - the operation + // will be 'completed' when the client closes the pipe. + waiting_for_close_ = true; + DWORD bytes_read = 0; + if (ReadFile(pipe_.get(), + &request_, + sizeof(request_), + &bytes_read, + &overlapped_)) { + return OnWaitForClientCloseComplete(bytes_read); + } else if (GetLastError() == ERROR_IO_PENDING) { + completion_handler_ = &RegistrationPipeState::OnWaitForClientCloseComplete; + return true; + } else { + PLOG(ERROR) << "ReadFile"; + return ResetConnection(); + } +} + +bool RegistrationPipeState::HandleRequest() { + if (get_named_pipe_client_process_id_proc_) { + // On Vista+ we can verify that the client is who they claim to be, thus + // preventing arbitrary processes from having us duplicate handles into + // other processes. + DWORD real_client_process_id = 0; + if (!get_named_pipe_client_process_id_proc_(pipe_.get(), + &real_client_process_id)) { + PLOG(ERROR) << "GetNamedPipeClientProcessId"; + } else if (real_client_process_id != request_.client_process_id) { + LOG(ERROR) << "Client process ID from request (" + << request_.client_process_id + << ") does not match pipe client process ID (" + << real_client_process_id << ")."; + return ResetConnection(); + } + } + + ScopedKernelHANDLE client_process( + OpenProcess(PROCESS_ALL_ACCESS, false, request_.client_process_id)); + if (!client_process.is_valid()) { + if (ImpersonateNamedPipeClient(pipe_.get())) { + client_process.reset( + OpenProcess(PROCESS_ALL_ACCESS, false, request_.client_process_id)); + RevertToSelf(); + } + } + + if (!client_process.is_valid()) { + LOG(ERROR) << "Failed to open client process."; + return ResetConnection(); + } + + memset(&response_, 0, sizeof(response_)); + + HANDLE request_report_event = nullptr; + HANDLE report_complete_event = nullptr; + + if (!delegate_->RegisterClient(client_process.Pass(), + request_.crashpad_info_address, + &request_report_event, + &report_complete_event)) { + return ResetConnection(); + } + + // A handle has at most 32 significant bits, though its type is void*. Thus we + // truncate it here. An interesting exception is INVALID_HANDLE_VALUE, which + // is '-1'. It is still safe to truncate it from 0xFFFFFFFFFFFFFFFF to + // 0xFFFFFFFF, but a 64-bit client receiving that value must correctly sign + // extend it. + response_.request_report_event = + reinterpret_cast(request_report_event); + response_.report_complete_event = + reinterpret_cast(report_complete_event); + return IssueWrite(); +} + +bool RegistrationPipeState::ResetConnection() { + memset(&request_, 0, sizeof(request_)); + waiting_for_close_ = false; + + if (!DisconnectNamedPipe(pipe_.get())) { + PLOG(ERROR) << "DisconnectNamedPipe"; + return false; + } else { + return IssueConnect(); + } +} + +} // namespace crashpad diff --git a/handler/win/registration_pipe_state.h b/handler/win/registration_pipe_state.h new file mode 100644 index 00000000..738f8c32 --- /dev/null +++ b/handler/win/registration_pipe_state.h @@ -0,0 +1,112 @@ +// 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_HANDLER_WIN_REGISTRATION_PIPE_STATE_H_ +#define CRASHPAD_HANDLER_WIN_REGISTRATION_PIPE_STATE_H_ + +#include + +#include "base/basictypes.h" +#include "client/registration_protocol_win.h" +#include "handler/win/registration_server.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { + +//! \brief Implements the state and state transitions of a single named pipe for +//! the Crashpad client registration protocol for Windows. Each pipe +//! instance may handle a single client connection at a time. After each +//! connection completes, whether successfully or otherwise, the instance +//! will attempt to return to a listening state, ready for a new client +//! connection. +class RegistrationPipeState { + public: + //! \brief Initializes the state for a single pipe instance. The client must + //! call Initialize() before clients may connect to this pipe. + //! \param[in] pipe The named pipe to listen on. + //! \param[in] delegate The delegate that will be used to handle requests. + RegistrationPipeState(ScopedFileHANDLE pipe, + RegistrationServer::Delegate* delegate); + ~RegistrationPipeState(); + + //! \brief Places the pipe in the running state, ready to receive and process + //! client connections. Before destroying a running RegistrationPipeState + //! instance you must invoke Stop() and then wait for completion_event() + //! to be signaled one last time. + //! \return Returns true if successful, in which case the client must observe + //! completion_event() and invoke OnCompletion() whenever it is signaled. + bool Initialize(); + + //! \brief Cancels any pending asynchronous operations. After invoking this + //! method you must wait for completion_event() to be signaled before + //! destroying the instance. + void Stop(); + + //! \brief Returns an event handle that will be signaled whenever an + //! asynchronous operation associated with this instance completes. + HANDLE completion_event() { return event_.get(); } + + //! \brief Must be called by the client whenever completion_event() is + //! signaled. + //! \return Returns true if the pipe is still in the running state. Otherwise, + //! a permanent failure has occurred and the instance may be immediately + //! destroyed. + bool OnCompletion(); + + private: + using AsyncCompletionHandler = + bool (RegistrationPipeState::*)(DWORD bytes_transferred); + + // State transition handlers. Return true if the pipe is still valid. + + bool OnConnectComplete(DWORD /* bytes_transferred */); + bool OnReadComplete(DWORD bytes_transferred); + bool OnWriteComplete(DWORD bytes_transferred); + bool OnWaitForClientCloseComplete(DWORD bytes_transferred); + + // Pipe operations. Return true if the pipe is still valid. + + // Prepares the pipe to accept a new client connecion. + bool IssueConnect(); + // Reads into |request_|. + bool IssueRead(); + // Writes from |response_|. + bool IssueWrite(); + // Issues a final ReadFile() that is expected to be terminated when the client + // closes the pipe. + bool IssueWaitForClientClose(); + // Processes |request_| using |delegate_| and stores the result in + // |response_|. + bool HandleRequest(); + // Closes the active connection and invokes IssueConnect(). + bool ResetConnection(); + + RegistrationRequest request_; + RegistrationResponse response_; + // The state transition handler to be invoked when the active asynchronous + // operation completes. + AsyncCompletionHandler completion_handler_; + OVERLAPPED overlapped_; + ScopedKernelHANDLE event_; + ScopedFileHANDLE pipe_; + bool waiting_for_close_; + RegistrationServer::Delegate* delegate_; + decltype(GetNamedPipeClientProcessId)* get_named_pipe_client_process_id_proc_; + + DISALLOW_COPY_AND_ASSIGN(RegistrationPipeState); +}; + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_WIN_REGISTRATION_PIPE_STATE_H_ diff --git a/handler/win/registration_pipe_state_test.cc b/handler/win/registration_pipe_state_test.cc new file mode 100644 index 00000000..276b6db8 --- /dev/null +++ b/handler/win/registration_pipe_state_test.cc @@ -0,0 +1,259 @@ +// 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 "handler/win/registration_pipe_state.h" + +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "client/crashpad_info.h" +#include "client/registration_protocol_win.h" +#include "gtest/gtest.h" +#include "handler/win/registration_test_base.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { +namespace test { +namespace { + +class RegistrationRegistrationPipeStateTest : public RegistrationTestBase { + public: + RegistrationRegistrationPipeStateTest() : pipe_state_() {} + + void SetUp() override { + ScopedFileHANDLE pipe( + CreateNamedPipe(pipe_name().c_str(), + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | + FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + 1, + 512, // nOutBufferSize + 512, // nInBufferSize + 20, // nDefaultTimeOut + nullptr)); // lpSecurityAttributes + ASSERT_TRUE(pipe.is_valid()); + pipe_state_.reset(new RegistrationPipeState(pipe.Pass(), &delegate())); + } + + ~RegistrationRegistrationPipeStateTest() override {} + + RegistrationPipeState& pipe_state() { + DCHECK(pipe_state_.get()); + return *pipe_state_; + } + + private: + scoped_ptr pipe_state_; + DISALLOW_COPY_AND_ASSIGN(RegistrationRegistrationPipeStateTest); +}; + +TEST_F(RegistrationRegistrationPipeStateTest, CancelIoWhenConnectIsComplete) { + // -> Connecting + ASSERT_TRUE(pipe_state().Initialize()); + + ScopedFileHANDLE client(Connect()); + + ASSERT_TRUE(client.is_valid()); + + // Connect completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Connecting -> Stopping + pipe_state().Stop(); + + // Stop completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); +} + +TEST_F(RegistrationRegistrationPipeStateTest, CancelIoWhenReadIsComplete) { + // -> Connecting + ASSERT_TRUE(pipe_state().Initialize()); + + ScopedFileHANDLE client(Connect()); + + ASSERT_TRUE(client.is_valid()); + + // Connect completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Connecting -> Reading + ASSERT_TRUE(pipe_state().OnCompletion()); + + RegistrationRequest request = {0}; + CrashpadInfo crashpad_info; + request.client_process_id = GetCurrentProcessId(); + request.crashpad_info_address = + reinterpret_cast(&crashpad_info); + + ASSERT_TRUE(WriteRequest(client.get(), &request, sizeof(request))); + + // Read completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Reading -> Stopping + pipe_state().Stop(); + + // Stop completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); +} + +TEST_F(RegistrationRegistrationPipeStateTest, CancelIoWhenWriteIsComplete) { + // -> Connecting + ASSERT_TRUE(pipe_state().Initialize()); + + ScopedFileHANDLE client(Connect()); + + ASSERT_TRUE(client.is_valid()); + + // Connect completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Connecting -> Reading + ASSERT_TRUE(pipe_state().OnCompletion()); + + RegistrationRequest request = {0}; + CrashpadInfo crashpad_info; + request.client_process_id = GetCurrentProcessId(); + request.crashpad_info_address = + reinterpret_cast(&crashpad_info); + + ASSERT_TRUE(WriteRequest(client.get(), &request, sizeof(request))); + + // Read completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Reading -> Writing -> Waiting for Close + ASSERT_TRUE(pipe_state().OnCompletion()); + + RegistrationResponse response = {0}; + ASSERT_TRUE(ReadResponse(client.get(), &response)); + + // Waiting for Close -> Stopping + pipe_state().Stop(); + + // Stop completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); +} + +TEST_F(RegistrationRegistrationPipeStateTest, CancelIoWhenCloseIsComplete) { + // -> Connecting + ASSERT_TRUE(pipe_state().Initialize()); + + ScopedFileHANDLE client(Connect()); + + ASSERT_TRUE(client.is_valid()); + + // Connect completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Connecting -> Reading + ASSERT_TRUE(pipe_state().OnCompletion()); + + RegistrationRequest request = {0}; + CrashpadInfo crashpad_info; + request.client_process_id = GetCurrentProcessId(); + request.crashpad_info_address = + reinterpret_cast(&crashpad_info); + + ASSERT_TRUE(WriteRequest(client.get(), &request, sizeof(request))); + + // Read completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Reading -> Writing -> Waiting for Close + ASSERT_TRUE(pipe_state().OnCompletion()); + + RegistrationResponse response = {0}; + ASSERT_TRUE(ReadResponse(client.get(), &response)); + + client.reset(); + + // Wait for close completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Waiting for Close -> Stopping + pipe_state().Stop(); + + // Stop completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); +} + +TEST_F(RegistrationRegistrationPipeStateTest, FullCycle) { + // -> Connecting + ASSERT_TRUE(pipe_state().Initialize()); + + ScopedFileHANDLE client(Connect()); + + ASSERT_TRUE(client.is_valid()); + + // Connect completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Connecting -> Reading + ASSERT_TRUE(pipe_state().OnCompletion()); + + RegistrationRequest request = {0}; + CrashpadInfo crashpad_info; + request.client_process_id = GetCurrentProcessId(); + request.crashpad_info_address = + reinterpret_cast(&crashpad_info); + + ASSERT_TRUE(WriteRequest(client.get(), &request, sizeof(request))); + + // Read completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Reading -> Writing -> Waiting for Close + ASSERT_TRUE(pipe_state().OnCompletion()); + + RegistrationResponse response = {0}; + ASSERT_TRUE(ReadResponse(client.get(), &response)); + + client.reset(); + + // Wait for close completion. + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); + + // Waiting for Close -> Reset -> Connecting + ASSERT_TRUE(pipe_state().OnCompletion()); + + client = Connect(); + ASSERT_TRUE(client.is_valid()); + + pipe_state().Stop(); + + ASSERT_EQ(WAIT_OBJECT_0, + WaitForSingleObject(pipe_state().completion_event(), INFINITE)); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/handler/win/registration_server.cc b/handler/win/registration_server.cc new file mode 100644 index 00000000..583af243 --- /dev/null +++ b/handler/win/registration_server.cc @@ -0,0 +1,136 @@ +// 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 "handler/win/registration_server.h" + +#include + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "handler/win/registration_pipe_state.h" +#include "util/stdlib/pointer_container.h" + +namespace crashpad { + +RegistrationServer::RegistrationServer() : stop_event_() { + stop_event_.reset(CreateEvent(nullptr, false, false, nullptr)); + DPCHECK(stop_event_.is_valid()); +} + +RegistrationServer::~RegistrationServer() { +} + +bool RegistrationServer::Run(const base::string16& pipe_name, + Delegate* delegate) { + if (!stop_event_.is_valid()) { + LOG(ERROR) << "Failed to create stop_event_."; + return false; + } + + PointerVector pipes; + std::vector handles; + + const int kNumPipes = 3; + + // Create the named pipes. + for (int i = 0; i < kNumPipes; ++i) { + DWORD open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED; + if (pipes.size() == 0) + open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE; + ScopedFileHANDLE pipe( + CreateNamedPipe(pipe_name.c_str(), + open_mode, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + kNumPipes, + 512, // nOutBufferSize + 512, // nInBufferSize + 20, // nDefaultTimeOut + nullptr)); // lpSecurityAttributes + if (pipe.is_valid()) { + scoped_ptr pipe_state( + new RegistrationPipeState(pipe.Pass(), delegate)); + if (pipe_state->Initialize()) { + pipes.push_back(pipe_state.release()); + handles.push_back(pipes.back()->completion_event()); + } + } else { + PLOG(ERROR) << "CreateNamedPipe"; + } + } + + if (pipes.size() == 0) { + LOG(ERROR) << "Failed to initialize any pipes."; + return false; + } + + delegate->OnStarted(); + + // Add stop_event_ to the list of events we will observe. + handles.push_back(stop_event_.get()); + + bool stopped = false; + + // Run the main loop, dispatching completion event signals to the pipe + // instances. + while (true) { + DWORD wait_result = WaitForMultipleObjects( + static_cast(handles.size()), handles.data(), false, INFINITE); + if (wait_result >= WAIT_OBJECT_0 && + wait_result < WAIT_OBJECT_0 + pipes.size()) { + int index = wait_result - WAIT_OBJECT_0; + // Handle a completion event. + if (!pipes[index]->OnCompletion()) { + pipes.erase(pipes.begin() + index); + handles.erase(handles.begin() + index); + } + if (pipes.size()) + continue; + // Exit due to all pipes having failed. + } else if (wait_result == WAIT_OBJECT_0 + pipes.size()) { + // Exit due to stop_event_. + stopped = true; + } else if (wait_result == WAIT_FAILED) { + // Exit due to error. + PLOG(ERROR) << "WaitForMultipleObjects"; + } else { + // Exit due to unexpected return code. + NOTREACHED(); + } + break; + } + + // Remove |stop_event_| from the wait list. + handles.pop_back(); + + // Cancel any ongoing asynchronous operations. + for (auto& pipe : pipes) { + pipe->Stop(); + } + + // Wait until all of the pipe instances are ready to be destroyed. + DWORD wait_result = WaitForMultipleObjects( + static_cast(handles.size()), handles.data(), true, INFINITE); + PCHECK(wait_result != WAIT_FAILED); + DCHECK_GE(wait_result, WAIT_OBJECT_0); + DCHECK_LT(wait_result, WAIT_OBJECT_0 + handles.size()); + + return stopped; +} + +void RegistrationServer::Stop() { + if (!SetEvent(stop_event_.get())) + PLOG(FATAL) << "SetEvent"; +} + +} // namespace crashpad diff --git a/handler/win/registration_server.h b/handler/win/registration_server.h new file mode 100644 index 00000000..0391ac1b --- /dev/null +++ b/handler/win/registration_server.h @@ -0,0 +1,83 @@ +// 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_HANDLER_WIN_REGISTRATION_SERVER_H_ +#define CRASHPAD_HANDLER_WIN_REGISTRATION_SERVER_H_ + +#include + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "util/win/address_types.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { + +//! \brief Implements the server side of the Crashpad client registration +//! protocol for Windows. +class RegistrationServer { + public: + //! \brief Handles registration requests. + class Delegate { + public: + virtual ~Delegate() {} + + //! \brief Receives notification that clients may now connect to the named + //! pipe. + virtual void OnStarted() = 0; + + //! \brief Responds to a request to register a client process for crash + //! handling. + //! + //! \param[in] client_process The client that is making the request. + //! \param[in] crashpad_info_address The address of a CrashpadInfo structure + //! in the client's address space. + //! \param[out] request_dump_event A `HANDLE`, valid in the client process, + //! to an event that, when signaled, triggers a crash report. + //! \param[out] dump_complete_event A `HANDLE`, valid in the client process, + //! to an event that will be signaled when the crash report has been + //! successfully captured. + virtual bool RegisterClient(ScopedKernelHANDLE client_process, + WinVMAddress crashpad_info_address, + HANDLE* request_dump_event, + HANDLE* dump_complete_event) = 0; + }; + + //! \brief Instantiates a client registration server. + RegistrationServer(); + ~RegistrationServer(); + + //! \brief Runs the RegistrationServer, receiving and processing requests and + //! sending responses. Blocks until Stop() is invoked. + //! + //! \param[in] pipe_name The pipe name to be used by the server. + //! \param[in] delegate The delegate to be used to handle requests. + //! + //! \return Returns `true` if it terminates due to invocation of Stop(). + //! `false` indicates early termination due to a failure. + bool Run(const base::string16& pipe_name, Delegate* delegate); + + //! \brief Stops the RegistrationServer. Returns immediately. The instance + //! must not be destroyed until the call to Run() completes. + void Stop(); + + private: + ScopedKernelHANDLE stop_event_; + + DISALLOW_COPY_AND_ASSIGN(RegistrationServer); +}; + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_WIN_REGISTRATION_SERVER_H_ diff --git a/handler/win/registration_server_test.cc b/handler/win/registration_server_test.cc new file mode 100644 index 00000000..dddfcf43 --- /dev/null +++ b/handler/win/registration_server_test.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 "handler/win/registration_server.h" + +#include + +#include + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "client/crashpad_info.h" +#include "client/registration_protocol_win.h" +#include "gtest/gtest.h" +#include "handler/win/registration_test_base.h" +#include "util/thread/thread.h" +#include "util/win/address_types.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { +namespace test { +namespace { + +// Runs the RegistrationServer on a background thread. +class RunServerThread : public Thread { + public: + // Instantiates a thread which will invoke server->Run(pipe_name, delegate). + RunServerThread(RegistrationServer* server, + const base::string16& pipe_name, + RegistrationServer::Delegate* delegate) + : server_(server), pipe_name_(pipe_name), delegate_(delegate) {} + ~RunServerThread() override {} + + private: + // Thread: + void ThreadMain() override { server_->Run(pipe_name_, delegate_); } + + RegistrationServer* server_; + base::string16 pipe_name_; + RegistrationServer::Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(RunServerThread); +}; + +class RegistrationServerTest : public RegistrationTestBase { + public: + RegistrationServerTest() + : server_(), server_thread_(&server_, pipe_name(), &delegate()) {} + + RegistrationServer& server() { return server_; } + Thread& server_thread() { return server_thread_; } + + private: + RegistrationServer server_; + RunServerThread server_thread_; + + DISALLOW_COPY_AND_ASSIGN(RegistrationServerTest); +}; + +// During destruction, ensures that the server is stopped and the background +// thread joined. +class ScopedStopServerAndJoinThread { + public: + explicit ScopedStopServerAndJoinThread(RegistrationServer* server, + Thread* thread) + : server_(server), thread_(thread) {} + ~ScopedStopServerAndJoinThread() { + server_->Stop(); + thread_->Join(); + } + + private: + RegistrationServer* server_; + Thread* thread_; + DISALLOW_COPY_AND_ASSIGN(ScopedStopServerAndJoinThread); +}; + +TEST_F(RegistrationServerTest, Instantiate) { +} + +TEST_F(RegistrationServerTest, StartAndStop) { + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); +} + +TEST_F(RegistrationServerTest, StopWhileConnected) { + ScopedFileHANDLE connection; + { + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); + connection = Connect(); + ASSERT_TRUE(connection.is_valid()); + // Leaving this scope causes the server to be stopped, while the connection + // is still open. + } +} + +TEST_F(RegistrationServerTest, Register) { + RegistrationRequest request = {0}; + RegistrationResponse response = {0}; + CrashpadInfo crashpad_info; + request.client_process_id = GetCurrentProcessId(); + request.crashpad_info_address = + reinterpret_cast(&crashpad_info); + + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); + + ASSERT_TRUE(SendRequest(Connect(), &request, sizeof(request), &response)); + + ASSERT_EQ(1, delegate().registered_processes().size()); + VerifyRegistration(*delegate().registered_processes()[0], request, response); +} + +TEST_F(RegistrationServerTest, ForgedClientId) { + // Skip this test on pre-Vista as the forged PID detection is not supported + // there. + OSVERSIONINFO vi = {0}; + vi.dwOSVersionInfoSize = sizeof(vi); + GetVersionEx(&vi); + if (vi.dwMajorVersion < 6) + return; + + RegistrationRequest request = {0}; + RegistrationResponse response = {0}; + CrashpadInfo crashpad_info; + // Note that we forge the PID here. + request.client_process_id = GetCurrentProcessId() + 1; + request.crashpad_info_address = + reinterpret_cast(&crashpad_info); + + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); + + ASSERT_FALSE(SendRequest(Connect(), &request, sizeof(request), &response)); + ASSERT_EQ(0, delegate().registered_processes().size()); + + // Correct the PID and verify that this was the only reason we failed. + request.client_process_id = GetCurrentProcessId(); + ASSERT_TRUE(SendRequest(Connect(), &request, sizeof(request), &response)); + ASSERT_EQ(1, delegate().registered_processes().size()); + VerifyRegistration(*delegate().registered_processes()[0], request, response); +} + +TEST_F(RegistrationServerTest, RegisterClientFails) { + RegistrationRequest request = {0}; + RegistrationResponse response = {0}; + CrashpadInfo crashpad_info; + request.client_process_id = GetCurrentProcessId(); + request.crashpad_info_address = + reinterpret_cast(&crashpad_info); + + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); + + // Simulate some failures + delegate().set_fail_mode(true); + for (int i = 0; i < 10; ++i) { + ASSERT_FALSE(SendRequest(Connect(), &request, sizeof(request), &response)); + ASSERT_EQ(0, delegate().registered_processes().size()); + } + + // Now verify that a valid response may still be processed. + delegate().set_fail_mode(false); + ASSERT_TRUE(SendRequest(Connect(), &request, sizeof(request), &response)); + + ASSERT_EQ(1, delegate().registered_processes().size()); + VerifyRegistration(*delegate().registered_processes()[0], request, response); +} + +TEST_F(RegistrationServerTest, BadRequests) { + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); + + RegistrationRequest request = {0}; + RegistrationResponse response = {0}; + CrashpadInfo crashpad_info; + request.client_process_id = GetCurrentProcessId(); + request.crashpad_info_address = + reinterpret_cast(&crashpad_info); + + // Concatenate a valid request with a single byte of garbage. + std::vector extra_long; + extra_long.insert(extra_long.begin(), + reinterpret_cast(&request), + reinterpret_cast(&request) + sizeof(request)); + extra_long.push_back('x'); + + for (int i = 0; i < 10; ++i) { + ASSERT_FALSE(SendRequest(Connect(), "a", 1, &response)); + ASSERT_FALSE(SendRequest( + Connect(), extra_long.data(), extra_long.size(), &response)); + ASSERT_TRUE(Connect().is_valid()); + } + + // Now verify that a valid response may still be processed. + + ASSERT_TRUE(SendRequest(Connect(), &request, sizeof(request), &response)); + + ASSERT_EQ(1, delegate().registered_processes().size()); + VerifyRegistration(*delegate().registered_processes()[0], request, response); +} + +TEST_F(RegistrationServerTest, OverlappingRequests) { + server_thread().Start(); + ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( + &server(), &server_thread()); + ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); + + RegistrationRequest request = {0}; + RegistrationResponse response_1 = {0}; + RegistrationResponse response_2 = {0}; + RegistrationResponse response_3 = {0}; + CrashpadInfo crashpad_info; + request.client_process_id = GetCurrentProcessId(); + request.crashpad_info_address = + reinterpret_cast(&crashpad_info); + + ScopedFileHANDLE connection_1 = Connect(); + ASSERT_TRUE(connection_1.is_valid()); + ScopedFileHANDLE connection_2 = Connect(); + ASSERT_TRUE(connection_2.is_valid()); + ScopedFileHANDLE connection_3 = Connect(); + ASSERT_TRUE(connection_3.is_valid()); + + ASSERT_FALSE(SendRequest(connection_1.Pass(), "a", 1, &response_1)); + + ASSERT_TRUE( + SendRequest(connection_2.Pass(), &request, sizeof(request), &response_2)); + + ASSERT_TRUE(Connect().is_valid()); + + ASSERT_TRUE( + SendRequest(connection_3.Pass(), &request, sizeof(request), &response_3)); + + ASSERT_EQ(2, delegate().registered_processes().size()); + VerifyRegistration( + *delegate().registered_processes()[0], request, response_2); + VerifyRegistration( + *delegate().registered_processes()[1], request, response_3); +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/handler/win/registration_test_base.cc b/handler/win/registration_test_base.cc new file mode 100644 index 00000000..c7384064 --- /dev/null +++ b/handler/win/registration_test_base.cc @@ -0,0 +1,166 @@ +// 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 "handler/win/registration_test_base.h" + +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" + +namespace crashpad { +namespace test { + +RegistrationTestBase::MockDelegate::Entry::Entry( + ScopedKernelHANDLE client_process, + WinVMAddress crashpad_info_address, + uint32_t fake_request_dump_event, + uint32_t fake_dump_complete_event) + : client_process(client_process.Pass()), + crashpad_info_address(crashpad_info_address), + fake_request_dump_event(fake_request_dump_event), + fake_dump_complete_event(fake_dump_complete_event) { +} +RegistrationTestBase::MockDelegate::MockDelegate() + : started_event_(CreateEvent(nullptr, true, false, nullptr)), + registered_processes_(), + next_fake_handle_(1), + fail_(false) { + EXPECT_TRUE(started_event_.is_valid()); +} + +RegistrationTestBase::MockDelegate::~MockDelegate() { +} + +void RegistrationTestBase::MockDelegate::WaitForStart() { + DWORD wait_result = WaitForSingleObject(started_event_.get(), INFINITE); + if (wait_result == WAIT_FAILED) + PLOG(ERROR); + ASSERT_EQ(wait_result, WAIT_OBJECT_0); +} + +void RegistrationTestBase::MockDelegate::OnStarted() { + EXPECT_EQ(WAIT_TIMEOUT, WaitForSingleObject(started_event_.get(), 0)); + SetEvent(started_event_.get()); +} + +bool RegistrationTestBase::MockDelegate::RegisterClient( + ScopedKernelHANDLE client_process, + WinVMAddress crashpad_info_address, + HANDLE* request_dump_event, + HANDLE* dump_complete_event) { + if (fail_) + return false; + + if (!request_dump_event || !dump_complete_event) { + ADD_FAILURE() << "NULL 'out' parameter."; + return false; + } + *request_dump_event = reinterpret_cast(next_fake_handle_++); + *dump_complete_event = reinterpret_cast(next_fake_handle_++); + + registered_processes_.push_back( + new Entry(client_process.Pass(), + crashpad_info_address, + reinterpret_cast(*request_dump_event), + reinterpret_cast(*dump_complete_event))); + return true; +} + +RegistrationTestBase::RegistrationTestBase() + : pipe_name_( + L"\\\\.\\pipe\\registration_server_test_pipe_" + + base::UTF8ToUTF16(base::StringPrintf("%08x", GetCurrentProcessId()))), + delegate_() { +} + +RegistrationTestBase::~RegistrationTestBase() { +} + +// Returns a pipe handle connected to the RegistrationServer. +ScopedFileHANDLE RegistrationTestBase::Connect() { + ScopedFileHANDLE pipe; + const int kMaxRetries = 5; + for (int retries = 0; !pipe.is_valid() && retries < kMaxRetries; ++retries) { + if (!WaitNamedPipe(pipe_name_.c_str(), NMPWAIT_WAIT_FOREVER)) + break; + pipe.reset(CreateFile(pipe_name_.c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, + NULL)); + if (pipe.is_valid()) { + DWORD mode = PIPE_READMODE_MESSAGE; + if (!SetNamedPipeHandleState(pipe.get(), + &mode, + nullptr, // don't set maximum bytes + nullptr)) { // don't set maximum time + pipe.reset(); + } + } + } + EXPECT_TRUE(pipe.is_valid()); + return pipe.Pass(); +} + +// Sends the provided request and receives a response via the provided pipe. +bool RegistrationTestBase::SendRequest(ScopedFileHANDLE pipe, + const void* request_buffer, + size_t request_size, + RegistrationResponse* response) { + return WriteRequest(pipe.get(), request_buffer, request_size) && + ReadResponse(pipe.get(), response); +} + +bool RegistrationTestBase::WriteRequest(HANDLE pipe, + const void* request_buffer, + size_t request_size) { + DWORD byte_count = 0; + if (!WriteFile(pipe, + request_buffer, + static_cast(request_size), + &byte_count, + nullptr)) { + return false; + } + + return byte_count == request_size; +} + +bool RegistrationTestBase::ReadResponse(HANDLE pipe, + RegistrationResponse* response) { + DWORD byte_count = 0; + if (!ReadFile(pipe, response, sizeof(*response), &byte_count, nullptr)) + return false; + return byte_count == sizeof(*response); +} + +// Verifies that the request and response match what was received and sent by +// the MockDelegate. +void RegistrationTestBase::VerifyRegistration( + const MockDelegate::Entry& registered_process, + const RegistrationRequest& request, + const RegistrationResponse& response) { + EXPECT_EQ(request.crashpad_info_address, + registered_process.crashpad_info_address); + EXPECT_EQ(registered_process.fake_request_dump_event, + response.request_report_event); + EXPECT_EQ(registered_process.fake_dump_complete_event, + response.report_complete_event); + EXPECT_EQ(request.client_process_id, + GetProcessId(registered_process.client_process.get())); +} + +} // namespace test +} // namespace crashpad diff --git a/handler/win/registration_test_base.h b/handler/win/registration_test_base.h new file mode 100644 index 00000000..be96ffcb --- /dev/null +++ b/handler/win/registration_test_base.h @@ -0,0 +1,114 @@ +// 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 + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "client/registration_protocol_win.h" +#include "gtest/gtest.h" +#include "handler/win/registration_server.h" +#include "util/stdlib/pointer_container.h" +#include "util/win/address_types.h" +#include "util/win/scoped_handle.h" + +namespace crashpad { +namespace test { + +class RegistrationTestBase : public testing::Test { + public: + // Simulates a registrar to collect requests from and feed responses to the + // RegistrationServer. + class MockDelegate : public RegistrationServer::Delegate { + public: + // Records a single simulated client registration. + struct Entry { + Entry(ScopedKernelHANDLE client_process, + WinVMAddress crashpad_info_address, + uint32_t fake_request_dump_event, + uint32_t fake_dump_complete_event); + + ScopedKernelHANDLE client_process; + WinVMAddress crashpad_info_address; + uint32_t fake_request_dump_event; + uint32_t fake_dump_complete_event; + }; + + MockDelegate(); + ~MockDelegate() override; + + // Blocks until RegistrationServer::Delegate::OnStarted is invoked. + void WaitForStart(); + + // RegistrationServer::Delegate: + void OnStarted() override; + + bool RegisterClient(ScopedKernelHANDLE client_process, + WinVMAddress crashpad_info_address, + HANDLE* request_dump_event, + HANDLE* dump_complete_event) override; + + // Provides access to the registered process data. + const std::vector registered_processes() { + return registered_processes_; + } + + // If true, causes RegisterClient to simulate registration failure. + void set_fail_mode(bool fail) { fail_ = fail; } + + private: + ScopedKernelHANDLE started_event_; + PointerVector registered_processes_; + uint32_t next_fake_handle_; + bool fail_; + + DISALLOW_COPY_AND_ASSIGN(MockDelegate); + }; + + RegistrationTestBase(); + ~RegistrationTestBase() override; + + MockDelegate& delegate() { return delegate_; } + base::string16 pipe_name() { return pipe_name_; } + + // Returns a pipe handle connected to the RegistrationServer. + ScopedFileHANDLE Connect(); + + // Sends the provided request and receives a response via the provided pipe. + bool SendRequest(ScopedFileHANDLE pipe, + const void* request_buffer, + size_t request_size, + RegistrationResponse* response); + + bool WriteRequest(HANDLE pipe, + const void* request_buffer, + size_t request_size); + + bool ReadResponse(HANDLE pipe, RegistrationResponse* response); + + // Verifies that the request and response match what was received and sent by + // the MockDelegate. + void VerifyRegistration(const MockDelegate::Entry& registered_process, + const RegistrationRequest& request, + const RegistrationResponse& response); + + private: + base::string16 pipe_name_; + MockDelegate delegate_; + + DISALLOW_COPY_AND_ASSIGN(RegistrationTestBase); +}; + +} // namespace test +} // namespace crashpad