Chromium Code Reviews| Index: handler/mac/exception_handler_server.cc |
| diff --git a/handler/mac/exception_handler_server.cc b/handler/mac/exception_handler_server.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..dc1b704a0f6663759acd33f3b81f3cd72bf12b69 |
| --- /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_); |
|
Robert Sesek
2014/12/29 16:17:08
To ensure that this method is only called once, wh
Mark Mentovai
2014/12/29 17:08:07
Robert Sesek wrote:
|
| + |
| + // 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"; |
|
Robert Sesek
2014/12/29 16:17:08
Won't this abort if an error occurs, contrary to t
Mark Mentovai
2014/12/29 17:08:07
Robert Sesek wrote:
|
| + } |
| + } |
| + |
| + // 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; |
|
Robert Sesek
2014/12/29 16:17:08
MIG_BAD_ARGUMENTS instead?
Mark Mentovai
2014/12/29 17:08:07
Robert Sesek wrote:
|
| + } |
| + |
| + // 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 |