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(¬ify_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