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); +}