1 // Copyright 2014 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "handler/mac/exception_handler_server.h"
16 
17 #include <utility>
18 
19 #include "base/logging.h"
20 #include "base/mac/mach_logging.h"
21 #include "util/mach/composite_mach_message_server.h"
22 #include "util/mach/mach_extensions.h"
23 #include "util/mach/mach_message.h"
24 #include "util/mach/mach_message_server.h"
25 #include "util/mach/notify_server.h"
26 
27 namespace crashpad {
28 
29 namespace {
30 
31 class ExceptionHandlerServerRun : public UniversalMachExcServer::Interface,
32                                   public NotifyServer::DefaultInterface {
33  public:
ExceptionHandlerServerRun(mach_port_t exception_port,mach_port_t notify_port,bool launchd,UniversalMachExcServer::Interface * exception_interface)34   ExceptionHandlerServerRun(
35       mach_port_t exception_port,
36       mach_port_t notify_port,
37       bool launchd,
38       UniversalMachExcServer::Interface* exception_interface)
39       : UniversalMachExcServer::Interface(),
40         NotifyServer::DefaultInterface(),
41         mach_exc_server_(this),
42         notify_server_(this),
43         composite_mach_message_server_(),
44         exception_interface_(exception_interface),
45         exception_port_(exception_port),
46         notify_port_(notify_port),
47         running_(true),
48         launchd_(launchd) {
49     composite_mach_message_server_.AddHandler(&mach_exc_server_);
50     composite_mach_message_server_.AddHandler(&notify_server_);
51   }
52 
~ExceptionHandlerServerRun()53   ~ExceptionHandlerServerRun() {
54   }
55 
Run()56   void Run() {
57     DCHECK(running_);
58 
59     kern_return_t kr;
60     if (!launchd_) {
61       // Request that a no-senders notification for exception_port_ be sent to
62       // notify_port_.
63       mach_port_t previous;
64       kr = mach_port_request_notification(mach_task_self(),
65                                           exception_port_,
66                                           MACH_NOTIFY_NO_SENDERS,
67                                           0,
68                                           notify_port_,
69                                           MACH_MSG_TYPE_MAKE_SEND_ONCE,
70                                           &previous);
71       MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_request_notification";
72       base::mac::ScopedMachSendRight previous_owner(previous);
73     }
74 
75     // A single CompositeMachMessageServer will dispatch both exception messages
76     // and the no-senders notification. Put both receive rights into a port set.
77     //
78     // A single receive right can’t be used because the notification request
79     // requires a send-once right, which would prevent the no-senders condition
80     // from ever existing. Using distinct receive rights also allows the handler
81     // methods to ensure that the messages they process were sent by a holder of
82     // the proper send right.
83     base::mac::ScopedMachPortSet server_port_set(
84         NewMachPort(MACH_PORT_RIGHT_PORT_SET));
85     CHECK(server_port_set.is_valid());
86 
87     kr = mach_port_insert_member(
88         mach_task_self(), exception_port_, server_port_set.get());
89     MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_member";
90 
91     kr = mach_port_insert_member(
92         mach_task_self(), notify_port_, server_port_set.get());
93     MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_port_insert_member";
94 
95     // Run the server in kOneShot mode so that running_ can be reevaluated after
96     // each message. Receipt of a valid no-senders notification causes it to be
97     // set to false.
98     while (running_) {
99       // This will result in a call to CatchMachException() or
100       // DoMachNotifyNoSenders() as appropriate.
101       mach_msg_return_t mr =
102           MachMessageServer::Run(&composite_mach_message_server_,
103                                  server_port_set.get(),
104                                  kMachMessageReceiveAuditTrailer,
105                                  MachMessageServer::kOneShot,
106                                  MachMessageServer::kReceiveLargeIgnore,
107                                  kMachMessageTimeoutWaitIndefinitely);
108 
109       // MACH_SEND_INVALID_DEST occurs when attempting to reply to a dead name.
110       // This can happen if a mach_exc or exc client disappears before a reply
111       // can be sent to it. That’s unusal for kernel-generated requests, but can
112       // easily happen if a task sends its own exception request (as
113       // SimulateCrash() does) and dies before the reply is sent.
114       MACH_CHECK(mr == MACH_MSG_SUCCESS || mr == MACH_SEND_INVALID_DEST, mr)
115           << "MachMessageServer::Run";
116     }
117   }
118 
119   // UniversalMachExcServer::Interface:
120 
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)121   kern_return_t CatchMachException(exception_behavior_t behavior,
122                                    exception_handler_t exception_port,
123                                    thread_t thread,
124                                    task_t task,
125                                    exception_type_t exception,
126                                    const mach_exception_data_type_t* code,
127                                    mach_msg_type_number_t code_count,
128                                    thread_state_flavor_t* flavor,
129                                    ConstThreadState old_state,
130                                    mach_msg_type_number_t old_state_count,
131                                    thread_state_t new_state,
132                                    mach_msg_type_number_t* new_state_count,
133                                    const mach_msg_trailer_t* trailer,
134                                    bool* destroy_complex_request) override {
135     if (exception_port != exception_port_) {
136       LOG(WARNING) << "exception port mismatch";
137       return KERN_FAILURE;
138     }
139 
140     return exception_interface_->CatchMachException(behavior,
141                                                     exception_port,
142                                                     thread,
143                                                     task,
144                                                     exception,
145                                                     code,
146                                                     code_count,
147                                                     flavor,
148                                                     old_state,
149                                                     old_state_count,
150                                                     new_state,
151                                                     new_state_count,
152                                                     trailer,
153                                                     destroy_complex_request);
154   }
155 
156   // NotifyServer::DefaultInterface:
157 
DoMachNotifyNoSenders(notify_port_t notify,mach_port_mscount_t mscount,const mach_msg_trailer_t * trailer)158   kern_return_t DoMachNotifyNoSenders(
159       notify_port_t notify,
160       mach_port_mscount_t mscount,
161       const mach_msg_trailer_t* trailer) override {
162     if (notify != notify_port_) {
163       // The message was received as part of a port set. This check ensures that
164       // only the authorized sender of the no-senders notification is able to
165       // stop the exception server. Otherwise, a malicious client would be able
166       // to craft and send a no-senders notification via its exception port, and
167       // cause the handler to stop processing exceptions and exit.
168       LOG(WARNING) << "notify port mismatch";
169       return KERN_FAILURE;
170     }
171 
172     running_ = false;
173 
174     return KERN_SUCCESS;
175   }
176 
177  private:
178   UniversalMachExcServer mach_exc_server_;
179   NotifyServer notify_server_;
180   CompositeMachMessageServer composite_mach_message_server_;
181   UniversalMachExcServer::Interface* exception_interface_;  // weak
182   mach_port_t exception_port_;  // weak
183   mach_port_t notify_port_;  // weak
184   bool running_;
185   bool launchd_;
186 
187   DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerRun);
188 };
189 
190 }  // namespace
191 
ExceptionHandlerServer(base::mac::ScopedMachReceiveRight receive_port,bool launchd)192 ExceptionHandlerServer::ExceptionHandlerServer(
193     base::mac::ScopedMachReceiveRight receive_port,
194     bool launchd)
195     : receive_port_(std::move(receive_port)),
196       notify_port_(NewMachPort(MACH_PORT_RIGHT_RECEIVE)),
197       launchd_(launchd) {
198   CHECK(receive_port_.is_valid());
199   CHECK(notify_port_.is_valid());
200 }
201 
~ExceptionHandlerServer()202 ExceptionHandlerServer::~ExceptionHandlerServer() {
203 }
204 
Run(UniversalMachExcServer::Interface * exception_interface)205 void ExceptionHandlerServer::Run(
206     UniversalMachExcServer::Interface* exception_interface) {
207   ExceptionHandlerServerRun run(
208       receive_port_.get(), notify_port_.get(), launchd_, exception_interface);
209   run.Run();
210 }
211 
Stop()212 void ExceptionHandlerServer::Stop() {
213   // Cause the exception handler server to stop running by sending it a
214   // synthesized no-senders notification.
215   //
216   // mach_no_senders_notification_t defines the receive side of this structure,
217   // with a trailer element that’s undesirable for the send side.
218   struct {
219     mach_msg_header_t header;
220     NDR_record_t ndr;
221     mach_msg_type_number_t mscount;
222   } no_senders_notification = {};
223   no_senders_notification.header.msgh_bits =
224       MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND_ONCE, 0);
225   no_senders_notification.header.msgh_size = sizeof(no_senders_notification);
226   no_senders_notification.header.msgh_remote_port = notify_port_.get();
227   no_senders_notification.header.msgh_local_port = MACH_PORT_NULL;
228   no_senders_notification.header.msgh_id = MACH_NOTIFY_NO_SENDERS;
229   no_senders_notification.ndr = NDR_record;
230   no_senders_notification.mscount = 0;
231 
232   kern_return_t kr = mach_msg(&no_senders_notification.header,
233                               MACH_SEND_MSG,
234                               sizeof(no_senders_notification),
235                               0,
236                               MACH_PORT_NULL,
237                               MACH_MSG_TIMEOUT_NONE,
238                               MACH_PORT_NULL);
239   MACH_CHECK(kr == KERN_SUCCESS, kr) << "mach_msg";
240 }
241 
242 }  // namespace crashpad
243