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 "util/mach/mach_message.h"
16 
17 #include <AvailabilityMacros.h>
18 #include <bsm/libbsm.h>
19 
20 #include <limits>
21 
22 #include "base/logging.h"
23 #include "base/mac/mach_logging.h"
24 #include "util/misc/clock.h"
25 #include "util/misc/implicit_cast.h"
26 
27 namespace crashpad {
28 
29 namespace {
30 
31 constexpr int kNanosecondsPerMillisecond = 1E6;
32 
33 // TimerRunning() determines whether |deadline| has passed. If |deadline| is
34 // kMachMessageDeadlineWaitIndefinitely, |*timeout_options| is set to
35 // MACH_MSG_OPTION_NONE, |*remaining_ms| is set to MACH_MSG_TIMEOUT_NONE, and
36 // this function returns true. When used with mach_msg(), this will cause
37 // indefinite waiting. In any other case, |*timeout_options| is set to
38 // MACH_SEND_TIMEOUT | MACH_RCV_TIMEOUT, so mach_msg() will enforce a timeout
39 // specified by |*remaining_ms|. If |deadline| is in the future, |*remaining_ms|
40 // is set to the number of milliseconds remaining, which will always be a
41 // positive value, and this function returns true. If |deadline| is
42 // kMachMessageDeadlineNonblocking (indicating that no timer is in effect),
43 // |*remaining_ms| is set to zero and this function returns true. Otherwise,
44 // this function sets |*remaining_ms| to zero and returns false.
TimerRunning(uint64_t deadline,mach_msg_timeout_t * remaining_ms,mach_msg_option_t * timeout_options)45 bool TimerRunning(uint64_t deadline,
46                   mach_msg_timeout_t* remaining_ms,
47                   mach_msg_option_t* timeout_options) {
48   if (deadline == kMachMessageDeadlineWaitIndefinitely) {
49     *remaining_ms = MACH_MSG_TIMEOUT_NONE;
50     *timeout_options = MACH_MSG_OPTION_NONE;
51     return true;
52   }
53 
54   *timeout_options = MACH_SEND_TIMEOUT | MACH_RCV_TIMEOUT;
55 
56   if (deadline == kMachMessageDeadlineNonblocking) {
57     *remaining_ms = 0;
58     return true;
59   }
60 
61   uint64_t now = ClockMonotonicNanoseconds();
62 
63   if (now >= deadline) {
64     *remaining_ms = 0;
65   } else {
66     uint64_t remaining = deadline - now;
67 
68     // Round to the nearest millisecond, taking care not to overflow.
69     constexpr int kHalfMillisecondInNanoseconds =
70         kNanosecondsPerMillisecond / 2;
71     if (remaining <=
72         std::numeric_limits<uint64_t>::max() - kHalfMillisecondInNanoseconds) {
73       *remaining_ms = (remaining + kHalfMillisecondInNanoseconds) /
74                       kNanosecondsPerMillisecond;
75     } else {
76       *remaining_ms = remaining / kNanosecondsPerMillisecond;
77     }
78   }
79 
80   return *remaining_ms != 0;
81 }
82 
83 // This is an internal implementation detail of MachMessageWithDeadline(). It
84 // determines whether |deadline| has expired, and what timeout value and
85 // timeout-related options to pass to mach_msg() based on the value of
86 // |deadline|. mach_msg() will only be called if TimerRunning() returns true or
87 // if run_even_if_expired is true.
MachMessageWithDeadlineInternal(mach_msg_header_t * message,mach_msg_option_t options,mach_msg_size_t receive_size,mach_port_name_t receive_port,MachMessageDeadline deadline,mach_port_name_t notify_port,bool run_even_if_expired)88 mach_msg_return_t MachMessageWithDeadlineInternal(mach_msg_header_t* message,
89                                                   mach_msg_option_t options,
90                                                   mach_msg_size_t receive_size,
91                                                   mach_port_name_t receive_port,
92                                                   MachMessageDeadline deadline,
93                                                   mach_port_name_t notify_port,
94                                                   bool run_even_if_expired) {
95   mach_msg_timeout_t remaining_ms;
96   mach_msg_option_t timeout_options;
97   if (!TimerRunning(deadline, &remaining_ms, &timeout_options) &&
98       !run_even_if_expired) {
99     // Simulate the timed-out return values from mach_msg().
100     if (options & MACH_SEND_MSG) {
101       return MACH_SEND_TIMED_OUT;
102     }
103     if (options & MACH_RCV_MSG) {
104       return MACH_RCV_TIMED_OUT;
105     }
106     return MACH_MSG_SUCCESS;
107   }
108 
109   // Turn off the passed-in timeout bits and replace them with the ones from
110   // TimerRunning(). Get the send_size value from message->msgh_size if sending
111   // a message.
112   return mach_msg(
113       message,
114       (options & ~(MACH_SEND_TIMEOUT | MACH_RCV_TIMEOUT)) | timeout_options,
115       options & MACH_SEND_MSG ? message->msgh_size : 0,
116       receive_size,
117       receive_port,
118       remaining_ms,
119       notify_port);
120 }
121 
122 }  // namespace
123 
MachMessageDeadlineFromTimeout(mach_msg_timeout_t timeout_ms)124 MachMessageDeadline MachMessageDeadlineFromTimeout(
125     mach_msg_timeout_t timeout_ms) {
126   switch (timeout_ms) {
127     case kMachMessageTimeoutNonblocking:
128       return kMachMessageDeadlineNonblocking;
129     case kMachMessageTimeoutWaitIndefinitely:
130       return kMachMessageDeadlineWaitIndefinitely;
131     default:
132       return ClockMonotonicNanoseconds() +
133              implicit_cast<uint64_t>(timeout_ms) * kNanosecondsPerMillisecond;
134   }
135 }
136 
MachMessageWithDeadline(mach_msg_header_t * message,mach_msg_option_t options,mach_msg_size_t receive_size,mach_port_name_t receive_port,MachMessageDeadline deadline,mach_port_name_t notify_port,bool run_even_if_expired)137 mach_msg_return_t MachMessageWithDeadline(mach_msg_header_t* message,
138                                           mach_msg_option_t options,
139                                           mach_msg_size_t receive_size,
140                                           mach_port_name_t receive_port,
141                                           MachMessageDeadline deadline,
142                                           mach_port_name_t notify_port,
143                                           bool run_even_if_expired) {
144   // mach_msg() actaully does return MACH_MSG_SUCCESS when not asked to send or
145   // receive anything. See 10.9.5 xnu-1504.15.3/osfmk/ipc/mach_msg.c
146   // mach_msg_overwrite_trap().
147   mach_msg_return_t mr = MACH_MSG_SUCCESS;
148 
149   // Break up the send and receive into separate operations, so that the timeout
150   // can be recomputed from the deadline for each. Otherwise, the computed
151   // timeout will apply individually to the send and then to the receive, and
152   // the desired deadline could be exceeded.
153   //
154   // During sends, always set MACH_SEND_INTERRUPT, and during receives, always
155   // set MACH_RCV_INTERRUPT. If the caller didn’t specify these options, the
156   // calls will be retried with a recomputed deadline. If these bits weren’t
157   // set, the libsyscall wrapper (10.9.5
158   // xnu-2422.115.4/libsyscall/mach/mach_msg.c mach_msg() would restart
159   // interrupted calls with the original timeout value computed from the
160   // deadline, which would no longer correspond to the actual deadline. If the
161   // caller did specify these bits, don’t restart anything, because the caller
162   // wants to be notified of any interrupted calls.
163 
164   if (options & MACH_SEND_MSG) {
165     do {
166       mr = MachMessageWithDeadlineInternal(
167           message,
168           (options & ~MACH_RCV_MSG) | MACH_SEND_INTERRUPT,
169           0,
170           MACH_PORT_NULL,
171           deadline,
172           notify_port,
173           run_even_if_expired);
174     } while (mr == MACH_SEND_INTERRUPTED && !(options & MACH_SEND_INTERRUPT));
175 
176     if (mr != MACH_MSG_SUCCESS) {
177       return mr;
178     }
179   }
180 
181   if (options & MACH_RCV_MSG) {
182     do {
183       mr = MachMessageWithDeadlineInternal(
184           message,
185           (options & ~MACH_SEND_MSG) | MACH_RCV_INTERRUPT,
186           receive_size,
187           receive_port,
188           deadline,
189           notify_port,
190           run_even_if_expired);
191     } while (mr == MACH_RCV_INTERRUPTED && !(options & MACH_RCV_INTERRUPT));
192   }
193 
194   return mr;
195 }
196 
PrepareMIGReplyFromRequest(const mach_msg_header_t * in_header,mach_msg_header_t * out_header)197 void PrepareMIGReplyFromRequest(const mach_msg_header_t* in_header,
198                                 mach_msg_header_t* out_header) {
199   out_header->msgh_bits =
200       MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(in_header->msgh_bits), 0);
201   out_header->msgh_size = sizeof(mig_reply_error_t);
202   out_header->msgh_remote_port = in_header->msgh_remote_port;
203   out_header->msgh_local_port = MACH_PORT_NULL;
204   out_header->msgh_reserved = 0;
205   out_header->msgh_id = in_header->msgh_id + 100;
206   reinterpret_cast<mig_reply_error_t*>(out_header)->NDR = NDR_record;
207 }
208 
SetMIGReplyError(mach_msg_header_t * out_header,kern_return_t error)209 void SetMIGReplyError(mach_msg_header_t* out_header, kern_return_t error) {
210   reinterpret_cast<mig_reply_error_t*>(out_header)->RetCode = error;
211 }
212 
MachMessageTrailerFromHeader(const mach_msg_header_t * header)213 const mach_msg_trailer_t* MachMessageTrailerFromHeader(
214     const mach_msg_header_t* header) {
215   vm_address_t header_address = reinterpret_cast<vm_address_t>(header);
216   vm_address_t trailer_address = header_address + round_msg(header->msgh_size);
217   return reinterpret_cast<const mach_msg_trailer_t*>(trailer_address);
218 }
219 
AuditPIDFromMachMessageTrailer(const mach_msg_trailer_t * trailer)220 pid_t AuditPIDFromMachMessageTrailer(const mach_msg_trailer_t* trailer) {
221   if (trailer->msgh_trailer_type != MACH_MSG_TRAILER_FORMAT_0) {
222     LOG(ERROR) << "unexpected msgh_trailer_type " << trailer->msgh_trailer_type;
223     return -1;
224   }
225   if (trailer->msgh_trailer_size <
226       REQUESTED_TRAILER_SIZE(kMachMessageReceiveAuditTrailer)) {
227     LOG(ERROR) << "small msgh_trailer_size " << trailer->msgh_trailer_size;
228     return -1;
229   }
230 
231   const mach_msg_audit_trailer_t* audit_trailer =
232       reinterpret_cast<const mach_msg_audit_trailer_t*>(trailer);
233 
234 #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8
235   pid_t audit_pid;
236   audit_token_to_au32(audit_trailer->msgh_audit,
237                       nullptr,
238                       nullptr,
239                       nullptr,
240                       nullptr,
241                       nullptr,
242                       &audit_pid,
243                       nullptr,
244                       nullptr);
245 #else
246   pid_t audit_pid = audit_token_to_pid(audit_trailer->msgh_audit);
247 #endif
248 
249   return audit_pid;
250 }
251 
MachMessageDestroyReceivedPort(mach_port_t port,mach_msg_type_name_t port_right_type)252 bool MachMessageDestroyReceivedPort(mach_port_t port,
253                                     mach_msg_type_name_t port_right_type) {
254   // This implements a subset of 10.10.5
255   // xnu-2782.40.9/libsyscall/mach/mach_msg.c mach_msg_destroy_port() that deals
256   // only with port rights that can be received in Mach messages.
257   switch (port_right_type) {
258     case MACH_MSG_TYPE_PORT_RECEIVE: {
259       kern_return_t kr = mach_port_mod_refs(
260           mach_task_self(), port, MACH_PORT_RIGHT_RECEIVE, -1);
261       if (kr != KERN_SUCCESS) {
262         MACH_LOG(ERROR, kr) << "mach_port_mod_refs";
263         return false;
264       }
265       return true;
266     }
267 
268     case MACH_MSG_TYPE_PORT_SEND:
269     case MACH_MSG_TYPE_PORT_SEND_ONCE: {
270       kern_return_t kr = mach_port_deallocate(mach_task_self(), port);
271       if (kr != KERN_SUCCESS) {
272         MACH_LOG(ERROR, kr) << "mach_port_deallocate";
273         return false;
274       }
275       return true;
276     }
277 
278     default: {
279       LOG(ERROR) << "unexpected port right type " << port_right_type;
280       return false;
281     }
282   }
283 }
284 
285 }  // namespace crashpad
286