From 588d7dba1ce2d18f56a0ad17f0dca636e6b1bff0 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Tue, 30 Dec 2014 14:23:47 -0500 Subject: [PATCH] Add the crashpad_handler executable. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit crashpad_handler is Crashpad’s exception handler server. Currently, it runs a loop to receive exceptions, and exits when it no longer has any clients. In the future, this will be extended to write and potentially upload dumps. The handler is expected to be started by its initial client via the CrashpadClient interface. R=rsesek@chromium.org Review URL: https://codereview.chromium.org/789693005 --- crashpad.gyp | 1 + handler/handler.gyp | 42 +++++ handler/mac/crashpad_handler.ad | 67 +++++++ handler/mac/exception_handler_server.cc | 238 ++++++++++++++++++++++++ handler/mac/exception_handler_server.h | 65 +++++++ handler/mac/main.cc | 120 ++++++++++++ 6 files changed, 533 insertions(+) create mode 100644 handler/handler.gyp create mode 100644 handler/mac/crashpad_handler.ad create mode 100644 handler/mac/exception_handler_server.cc create mode 100644 handler/mac/exception_handler_server.h create mode 100644 handler/mac/main.cc diff --git a/crashpad.gyp b/crashpad.gyp index cc935459..63735f24 100644 --- a/crashpad.gyp +++ b/crashpad.gyp @@ -21,6 +21,7 @@ 'dependencies': [ 'client/client.gyp:*', 'compat/compat.gyp:*', + 'handler/handler.gyp:*', 'minidump/minidump.gyp:*', 'snapshot/snapshot.gyp:*', 'tools/tools.gyp:*', diff --git a/handler/handler.gyp b/handler/handler.gyp new file mode 100644 index 00000000..a6ff39ce --- /dev/null +++ b/handler/handler.gyp @@ -0,0 +1,42 @@ +# 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. + +{ + 'conditions': [ + ['OS=="mac"', { + 'targets': [ + { + 'target_name': 'crashpad_handler', + 'type': 'executable', + 'dependencies': [ + '../compat/compat.gyp:compat', + '../third_party/mini_chromium/mini_chromium/base/base.gyp:base', + '../tools/tools.gyp:tool_support', + '../util/util.gyp:util', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'mac/exception_handler_server.cc', + 'mac/exception_handler_server.h', + 'mac/main.cc', + ], + }, + ], + }, { + 'targets': [], + }], + ], +} diff --git a/handler/mac/crashpad_handler.ad b/handler/mac/crashpad_handler.ad new file mode 100644 index 00000000..0b2b23f0 --- /dev/null +++ b/handler/mac/crashpad_handler.ad @@ -0,0 +1,67 @@ +// 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. + +:doctype: manpage + += crashpad_handler(8) + +== Name + +crashpad_handler - Crashpad’s exception handler server + +== Synopsis + +[verse] +*crashpad_handler* ['OPTION…'] + +== Description + +This program is Crashpad’s main exception-handling server. This server is +normally started by its initial client, and it performs a handshake with this +client via a pipe established by the client that is inherited by the server, +referenced by the *--handshake-fd* argument. During the handshake, the server +furnishes the client with a send right that the client may use as an exception +port. The server retains the corresponding receive right, which it monitors for +exception messages. When the receive right loses all senders, the server exits. + +It is not normally appropriate to invoke this program directly. Usually, it will +be invoked by a Crashpad client using the Crashpad client library. Arbitrary +programs may be run with a Crashpad handler by using run_with_crashpad(1) to +establish the Crashpad client environment before running a program. + +== Options +*-h*, *--handshake-fd*='FD':: +Perform the handshake with the initial client on the file descriptor at 'FD'. + +*--help*:: +Display help and exit. + +*--version*:: +Output version information and exit. + +== Exit Status + +*0*:: +Success. + +*1*:: +Failure, with a message printed to the standard error stream. + +== See Also + +catch_exception_tool(1), +generate_dump(1), +run_with_crashpad(1) + +include::../../tools/man_footer.ad[] diff --git a/handler/mac/exception_handler_server.cc b/handler/mac/exception_handler_server.cc new file mode 100644 index 00000000..dc1b704a --- /dev/null +++ b/handler/mac/exception_handler_server.cc @@ -0,0 +1,238 @@ +// 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 "handler/mac/exception_handler_server.h" + +#include "base/logging.h" +#include "base/mac/mach_logging.h" +#include "base/strings/stringprintf.h" +#include "util/mach/composite_mach_message_server.h" +#include "util/mach/exc_server_variants.h" +#include "util/mach/exception_behaviors.h" +#include "util/mach/mach_extensions.h" +#include "util/mach/mach_message.h" +#include "util/mach/mach_message_server.h" +#include "util/mach/notify_server.h" + +namespace crashpad { + +namespace { + +class ExceptionHandlerServerRun + : public UniversalMachExcServer::Interface, + public NotifyServer::Interface { + public: + explicit ExceptionHandlerServerRun(mach_port_t exception_port) + : UniversalMachExcServer::Interface(), + NotifyServer::Interface(), + mach_exc_server_(this), + notify_server_(this), + composite_mach_message_server_(), + exception_port_(exception_port), + notify_port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)), + running_(true) { + CHECK_NE(notify_port_, kMachPortNull); + + composite_mach_message_server_.AddHandler(&mach_exc_server_); + composite_mach_message_server_.AddHandler(¬ify_server_); + } + + ~ExceptionHandlerServerRun() { + } + + void Run() { + DCHECK(running_); + + // Request that a no-senders notification for exception_port_ be sent to + // notify_port_. + mach_port_t previous; + kern_return_t kr = + mach_port_request_notification(mach_task_self(), + exception_port_, + MACH_NOTIFY_NO_SENDERS, + 0, + notify_port_, + MACH_MSG_TYPE_MAKE_SEND_ONCE, + &previous); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_request_notification"; + + if (previous != MACH_PORT_NULL) { + kr = mach_port_deallocate(mach_task_self(), previous); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_deallocate"; + } + + // A single CompositeMachMessageServer will dispatch both exception messages + // and the no-senders notification. Put both receive rights into a port set. + // + // A single receive right can’t be used because the notification request + // requires a send-once right, which would prevent the no-senders condition + // from ever existing. Using distinct receive rights also allows the handler + // methods to ensure that the messages they process were sent by a holder of + // the proper send right. + base::mac::ScopedMachPortSet server_port_set( + NewMachPort(MACH_PORT_RIGHT_PORT_SET)); + + kr = mach_port_insert_member( + mach_task_self(), exception_port_, server_port_set); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_member"; + + kr = mach_port_insert_member( + mach_task_self(), notify_port_, server_port_set); + MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_member"; + + // Run the server in kOneShot mode so that running_ can be reevaluated after + // each message. Receipt of a valid no-senders notification causes it to be + // set to false. + while (running_) { + // This will result in a call to CatchMachException() or + // DoMachNotifyNoSenders() as appropriate. + mach_msg_return_t mr = + MachMessageServer::Run(&composite_mach_message_server_, + server_port_set, + MACH_MSG_OPTION_NONE, + MachMessageServer::kOneShot, + MachMessageServer::kReceiveLargeIgnore, + kMachMessageTimeoutWaitIndefinitely); + MACH_CHECK(mr == MACH_MSG_SUCCESS, mr) << "MachMessageServer::Run"; + } + } + + // UniversalMachExcServer::Interface: + + kern_return_t CatchMachException(exception_behavior_t behavior, + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const mach_exception_data_type_t* code, + mach_msg_type_number_t code_count, + thread_state_flavor_t* flavor, + const natural_t* old_state, + mach_msg_type_number_t old_state_count, + thread_state_t new_state, + mach_msg_type_number_t* new_state_count, + const mach_msg_trailer_t* trailer, + bool* destroy_complex_request) override { + *destroy_complex_request = true; + + if (exception_port != exception_port_) { + LOG(WARNING) << "exception port mismatch"; + return MIG_BAD_ID; + } + + // The expected behavior is EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, + // but it’s possible to deal with any exception behavior as long as it + // carries identity information (valid thread and task ports). + if (!ExceptionBehaviorHasIdentity(behavior)) { + LOG(WARNING) << base::StringPrintf( + "unexpected exception behavior 0x%x, rejecting", behavior); + return KERN_FAILURE; + } else if (behavior != (EXCEPTION_STATE_IDENTITY | kMachExceptionCodes)) { + LOG(WARNING) << base::StringPrintf( + "unexpected exception behavior 0x%x, proceeding", behavior); + } + + // TODO(mark): Implement. + + return ExcServerSuccessfulReturnValue(behavior, false); + } + + // NotifyServer::Interface: + + kern_return_t DoMachNotifyPortDeleted( + notify_port_t notify, + mach_port_name_t name, + const mach_msg_trailer_t* trailer) override { + return UnimplementedNotifyRoutine(notify); + } + + kern_return_t DoMachNotifyPortDestroyed(notify_port_t notify, + mach_port_t rights, + const mach_msg_trailer_t* trailer, + bool* destroy_request) override { + *destroy_request = true; + return UnimplementedNotifyRoutine(notify); + } + + kern_return_t DoMachNotifyNoSenders( + notify_port_t notify, + mach_port_mscount_t mscount, + const mach_msg_trailer_t* trailer) override { + if (notify != notify_port_) { + // The message was received as part of a port set. This check ensures that + // only the authorized sender of the no-senders notification is able to + // stop the exception server. Otherwise, a malicious client would be able + // to craft and send a no-senders notification via its exception port, and + // cause the handler to stop processing exceptions and exit. + LOG(WARNING) << "notify port mismatch"; + return MIG_BAD_ID; + } + + running_ = false; + + return KERN_SUCCESS; + } + + kern_return_t DoMachNotifySendOnce( + notify_port_t notify, + const mach_msg_trailer_t* trailer) override { + return UnimplementedNotifyRoutine(notify); + } + + kern_return_t DoMachNotifyDeadName( + notify_port_t notify, + mach_port_name_t name, + const mach_msg_trailer_t* trailer) override { + return UnimplementedNotifyRoutine(notify); + } + + private: + kern_return_t UnimplementedNotifyRoutine(notify_port_t notify) { + // Most of the routines in the notify subsystem are not expected to be + // called. + if (notify != notify_port_) { + LOG(WARNING) << "notify port mismatch"; + return MIG_BAD_ID; + } + + NOTREACHED(); + return KERN_FAILURE; + } + + UniversalMachExcServer mach_exc_server_; + NotifyServer notify_server_; + CompositeMachMessageServer composite_mach_message_server_; + mach_port_t exception_port_; // weak + base::mac::ScopedMachReceiveRight notify_port_; + bool running_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerRun); +}; + +} // namespace + +ExceptionHandlerServer::ExceptionHandlerServer() + : receive_port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)) { + CHECK_NE(receive_port_, kMachPortNull); +} + +ExceptionHandlerServer::~ExceptionHandlerServer() { +} + +void ExceptionHandlerServer::Run() { + ExceptionHandlerServerRun run(receive_port_); + run.Run(); +} + +} // namespace crashpad diff --git a/handler/mac/exception_handler_server.h b/handler/mac/exception_handler_server.h new file mode 100644 index 00000000..a0a5764a --- /dev/null +++ b/handler/mac/exception_handler_server.h @@ -0,0 +1,65 @@ +// 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_HANDLER_MAC_EXCEPTION_HANDLER_SERVER_H_ +#define CRASHPAD_HANDLER_MAC_EXCEPTION_HANDLER_SERVER_H_ + +#include "base/basictypes.h" + +#include + +#include "base/mac/scoped_mach_port.h" + +namespace crashpad { + +//! \brief Runs the main exception-handling server in Crashpad’s handler +//! process. +class ExceptionHandlerServer { + public: + ExceptionHandlerServer(); + ~ExceptionHandlerServer(); + + //! \brief Runs the exception-handling server. + //! + //! This method monitors \a receive_port_ for exception messages and + //! no-senders notifications. It continues running until it has no more + //! clients, indicated by the receipt of a no-senders notification. It is + //! important to assure that a send right has been transferred to a client + //! (or queued by `mach_msg()` to be sent to a client) prior to calling this + //! method, or it will detect that it is sender-less and return immediately. + //! + //! This method must only be called once on an ExceptionHandlerServer object. + //! + //! If an unexpected condition that prevents this method from functioning is + //! encountered, it will log a message and terminate execution. Receipt of an + //! invalid message on \a receive_port_ will cause a message to be logged, but + //! this method will continue running normally. + void Run(); + + //! \brief Returns the receive right that will be monitored for exception + //! messages. + //! + //! The caller does not take ownership of this port. The caller must not use + //! this port for any purpose other than to make send rights for clients. + mach_port_t receive_port() const { return receive_port_; } + + private: + base::mac::ScopedMachReceiveRight receive_port_; + + DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer); +}; + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_MAC_EXCEPTION_HANDLER_SERVER_H_ diff --git a/handler/mac/main.cc b/handler/mac/main.cc new file mode 100644 index 00000000..4dfecef2 --- /dev/null +++ b/handler/mac/main.cc @@ -0,0 +1,120 @@ +// 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 +#include +#include + +#include + +#include "tools/tool_support.h" +#include "handler/mac/exception_handler_server.h" +#include "util/mach/child_port_handshake.h" +#include "util/posix/close_stdio.h" +#include "util/stdlib/string_number_conversion.h" + +namespace crashpad { +namespace { + +void Usage(const std::string& me) { + fprintf(stderr, +"Usage: %s [OPTION]...\n" +"Crashpad's exception handler server.\n" +"\n" +" --handshake-fd=FD establish communication with the client over FD\n" +" --help display this help and exit\n" +" --version output version information and exit\n", + me.c_str()); + ToolSupport::UsageTail(me); +} + +int HandlerMain(int argc, char* argv[]) { + const std::string me(basename(argv[0])); + + enum OptionFlags { + // Long options without short equivalents. + kOptionLastChar = 255, + kOptionHandshakeFD, + + // Standard options. + kOptionHelp = -2, + kOptionVersion = -3, + }; + + struct { + int handshake_fd; + } options = {}; + options.handshake_fd = -1; + + const struct option long_options[] = { + {"handshake-fd", required_argument, nullptr, kOptionHandshakeFD}, + {"help", no_argument, nullptr, kOptionHelp}, + {"version", no_argument, nullptr, kOptionVersion}, + {nullptr, 0, nullptr, 0}, + }; + + int opt; + while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) { + switch (opt) { + case kOptionHandshakeFD: + if (!StringToNumber(optarg, &options.handshake_fd) || + options.handshake_fd < 0) { + ToolSupport::UsageHint(me, + "--handshake-fd requires a file descriptor"); + return EXIT_FAILURE; + } + break; + case kOptionHelp: + Usage(me); + return EXIT_SUCCESS; + case kOptionVersion: + ToolSupport::Version(me); + return EXIT_SUCCESS; + default: + ToolSupport::UsageHint(me, nullptr); + return EXIT_FAILURE; + } + } + argc -= optind; + argv += optind; + + if (options.handshake_fd < 0) { + ToolSupport::UsageHint(me, "--handshake-fd is required"); + return EXIT_FAILURE; + } + + if (argc) { + ToolSupport::UsageHint(me, nullptr); + return EXIT_FAILURE; + } + + CloseStdinAndStdout(); + + ExceptionHandlerServer exception_handler_server; + + ChildPortHandshake::RunClient(options.handshake_fd, + exception_handler_server.receive_port(), + MACH_MSG_TYPE_MAKE_SEND); + + exception_handler_server.Run(); + + return EXIT_SUCCESS; +} + +} // namespace +} // namespace crashpad + +int main(int argc, char* argv[]) { + return crashpad::HandlerMain(argc, argv); +}