| // 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 <utility> |
| |
| #include "base/logging.h" |
| #include "base/mac/mach_logging.h" |
| #include "util/mach/composite_mach_message_server.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::DefaultInterface { |
| public: |
| ExceptionHandlerServerRun( |
| mach_port_t exception_port, |
| mach_port_t notify_port, |
| bool launchd, |
| UniversalMachExcServer::Interface* exception_interface) |
| : UniversalMachExcServer::Interface(), |
| NotifyServer::DefaultInterface(), |
| mach_exc_server_(this), |
| notify_server_(this), |
| composite_mach_message_server_(), |
| exception_interface_(exception_interface), |
| exception_port_(exception_port), |
| notify_port_(notify_port), |
| running_(true), |
| launchd_(launchd) { |
| composite_mach_message_server_.AddHandler(&mach_exc_server_); |
| composite_mach_message_server_.AddHandler(¬ify_server_); |
| } |
| |
| ~ExceptionHandlerServerRun() { |
| } |
| |
| void Run() { |
| DCHECK(running_); |
| |
| kern_return_t kr; |
| if (!launchd_) { |
| // Request that a no-senders notification for exception_port_ be sent to |
| // notify_port_. |
| mach_port_t previous; |
| 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"; |
| base::mac::ScopedMachSendRight previous_owner(previous); |
| } |
| |
| // 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)); |
| CHECK(server_port_set.is_valid()); |
| |
| kr = mach_port_insert_member( |
| mach_task_self(), exception_port_, server_port_set.get()); |
| MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_member"; |
| |
| kr = mach_port_insert_member( |
| mach_task_self(), notify_port_, server_port_set.get()); |
| 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.get(), |
| kMachMessageReceiveAuditTrailer, |
| MachMessageServer::kOneShot, |
| MachMessageServer::kReceiveLargeIgnore, |
| kMachMessageTimeoutWaitIndefinitely); |
| |
| // MACH_SEND_INVALID_DEST occurs when attempting to reply to a dead name. |
| // This can happen if a mach_exc or exc client disappears before a reply |
| // can be sent to it. That’s unusal for kernel-generated requests, but can |
| // easily happen if a task sends its own exception request (as |
| // SimulateCrash() does) and dies before the reply is sent. |
| MACH_CHECK(mr == MACH_MSG_SUCCESS || mr == MACH_SEND_INVALID_DEST, 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, |
| ConstThreadState 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 { |
| if (exception_port != exception_port_) { |
| LOG(WARNING) << "exception port mismatch"; |
| return KERN_FAILURE; |
| } |
| |
| return exception_interface_->CatchMachException(behavior, |
| exception_port, |
| thread, |
| task, |
| exception, |
| code, |
| code_count, |
| flavor, |
| old_state, |
| old_state_count, |
| new_state, |
| new_state_count, |
| trailer, |
| destroy_complex_request); |
| } |
| |
| // NotifyServer::DefaultInterface: |
| |
| 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 KERN_FAILURE; |
| } |
| |
| running_ = false; |
| |
| return KERN_SUCCESS; |
| } |
| |
| private: |
| UniversalMachExcServer mach_exc_server_; |
| NotifyServer notify_server_; |
| CompositeMachMessageServer composite_mach_message_server_; |
| UniversalMachExcServer::Interface* exception_interface_; // weak |
| mach_port_t exception_port_; // weak |
| mach_port_t notify_port_; // weak |
| bool running_; |
| bool launchd_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerRun); |
| }; |
| |
| } // namespace |
| |
| ExceptionHandlerServer::ExceptionHandlerServer( |
| base::mac::ScopedMachReceiveRight receive_port, |
| bool launchd) |
| : receive_port_(std::move(receive_port)), |
| notify_port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)), |
| launchd_(launchd) { |
| CHECK(receive_port_.is_valid()); |
| CHECK(notify_port_.is_valid()); |
| } |
| |
| ExceptionHandlerServer::~ExceptionHandlerServer() { |
| } |
| |
| void ExceptionHandlerServer::Run( |
| UniversalMachExcServer::Interface* exception_interface) { |
| ExceptionHandlerServerRun run( |
| receive_port_.get(), notify_port_.get(), launchd_, exception_interface); |
| run.Run(); |
| } |
| |
| void ExceptionHandlerServer::Stop() { |
| // Cause the exception handler server to stop running by sending it a |
| // synthesized no-senders notification. |
| // |
| // mach_no_senders_notification_t defines the receive side of this structure, |
| // with a trailer element that’s undesirable for the send side. |
| struct { |
| mach_msg_header_t header; |
| NDR_record_t ndr; |
| mach_msg_type_number_t mscount; |
| } no_senders_notification = {}; |
| no_senders_notification.header.msgh_bits = |
| MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND_ONCE, 0); |
| no_senders_notification.header.msgh_size = sizeof(no_senders_notification); |
| no_senders_notification.header.msgh_remote_port = notify_port_.get(); |
| no_senders_notification.header.msgh_local_port = MACH_PORT_NULL; |
| no_senders_notification.header.msgh_id = MACH_NOTIFY_NO_SENDERS; |
| no_senders_notification.ndr = NDR_record; |
| no_senders_notification.mscount = 0; |
| |
| kern_return_t kr = mach_msg(&no_senders_notification.header, |
| MACH_SEND_MSG, |
| sizeof(no_senders_notification), |
| 0, |
| MACH_PORT_NULL, |
| MACH_MSG_TIMEOUT_NONE, |
| MACH_PORT_NULL); |
| MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_msg"; |
| } |
| |
| } // namespace crashpad |