1 // Copyright 2015 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/exception_types.h"
16 
17 #include <Availability.h>
18 #include <dlfcn.h>
19 #include <errno.h>
20 #include <kern/exc_resource.h>
21 #include <libproc.h>
22 #include <strings.h>
23 
24 #include "base/check_op.h"
25 #include "base/logging.h"
26 #include "base/mac/mach_logging.h"
27 #include "util/mac/mac_util.h"
28 #include "util/mach/mach_extensions.h"
29 #include "util/misc/no_cfi_icall.h"
30 #include "util/numeric/in_range_cast.h"
31 
32 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_9
33 
34 extern "C" {
35 
36 // proc_get_wakemon_params() is present in the OS X 10.9 SDK, but no declaration
37 // is provided. This provides a declaration and marks it for weak import if the
38 // deployment target is below 10.9.
39 int proc_get_wakemon_params(pid_t pid, int* rate_hz, int* flags)
40     __OSX_AVAILABLE_STARTING(__MAC_10_9, __IPHONE_7_0);
41 
42 // Redeclare the method without the availability annotation to suppress the
43 // -Wpartial-availability warning.
44 int proc_get_wakemon_params(pid_t pid, int* rate_hz, int* flags);
45 
46 }  // extern "C"
47 
48 #else
49 
50 namespace {
51 
52 using ProcGetWakemonParamsType = int (*)(pid_t, int*, int*);
53 
54 // The SDK doesn’t have proc_get_wakemon_params() to link against, even with
55 // weak import. This function returns a function pointer to it if it exists at
56 // runtime, or nullptr if it doesn’t. proc_get_wakemon_params() is looked up in
57 // the same module that provides proc_pidinfo().
GetProcGetWakemonParams()58 ProcGetWakemonParamsType GetProcGetWakemonParams() {
59   Dl_info dl_info;
60   if (!dladdr(reinterpret_cast<void*>(proc_pidinfo), &dl_info)) {
61     return nullptr;
62   }
63 
64   void* dl_handle =
65       dlopen(dl_info.dli_fname, RTLD_LAZY | RTLD_LOCAL | RTLD_NOLOAD);
66   if (!dl_handle) {
67     return nullptr;
68   }
69 
70   ProcGetWakemonParamsType proc_get_wakemon_params =
71       reinterpret_cast<ProcGetWakemonParamsType>(
72           dlsym(dl_handle, "proc_get_wakemon_params"));
73   return proc_get_wakemon_params;
74 }
75 
76 }  // namespace
77 
78 #endif
79 
80 namespace {
81 
82 // Wraps proc_get_wakemon_params(), calling it if the system provides it. It’s
83 // present on OS X 10.9 and later. If it’s not available, sets errno to ENOSYS
84 // and returns -1.
ProcGetWakemonParams(pid_t pid,int * rate_hz,int * flags)85 int ProcGetWakemonParams(pid_t pid, int* rate_hz, int* flags) {
86 #if __MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9
87   // proc_get_wakemon_params() isn’t in the SDK. Look it up dynamically.
88   static crashpad::NoCfiIcall<ProcGetWakemonParamsType> proc_get_wakemon_params(
89       GetProcGetWakemonParams());
90 #endif
91 
92 #if __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_9
93   // proc_get_wakemon_params() is definitely available if the deployment target
94   // is 10.9 or newer.
95   if (!proc_get_wakemon_params) {
96     errno = ENOSYS;
97     return -1;
98   }
99 #endif
100 
101   return proc_get_wakemon_params(pid, rate_hz, flags);
102 }
103 
104 }  // namespace
105 
106 namespace crashpad {
107 
ExcCrashRecoverOriginalException(mach_exception_code_t code_0,mach_exception_code_t * original_code_0,int * signal)108 exception_type_t ExcCrashRecoverOriginalException(
109     mach_exception_code_t code_0,
110     mach_exception_code_t* original_code_0,
111     int* signal) {
112   // 10.9.4 xnu-2422.110.17/bsd/kern/kern_exit.c proc_prepareexit() sets code[0]
113   // based on the signal value, original exception type, and low 20 bits of the
114   // original code[0] before calling xnu-2422.110.17/osfmk/kern/exception.c
115   // task_exception_notify() to raise an EXC_CRASH.
116   //
117   // The list of core-generating signals (as used in proc_prepareexit()’s call
118   // to hassigprop()) is in 10.9.4 xnu-2422.110.17/bsd/sys/signalvar.h sigprop:
119   // entires with SA_CORE are in the set. These signals are SIGQUIT, SIGILL,
120   // SIGTRAP, SIGABRT, SIGEMT, SIGFPE, SIGBUS, SIGSEGV, and SIGSYS. Processes
121   // killed for code-signing reasons will be killed by SIGKILL and are also
122   // eligible for EXC_CRASH handling, but processes killed by SIGKILL for other
123   // reasons are not.
124   if (signal) {
125     *signal = (code_0 >> 24) & 0xff;
126   }
127 
128   if (original_code_0) {
129     *original_code_0 = code_0 & 0xfffff;
130   }
131 
132   return (code_0 >> 20) & 0xf;
133 }
134 
ExcCrashCouldContainException(exception_type_t exception)135 bool ExcCrashCouldContainException(exception_type_t exception) {
136   // EXC_CRASH should never be wrapped in another EXC_CRASH.
137   //
138   // EXC_RESOURCE and EXC_GUARD are software exceptions that are never wrapped
139   // in EXC_CRASH. The only time EXC_CRASH is generated is for processes exiting
140   // due to an unhandled core-generating signal or being killed by SIGKILL for
141   // code-signing reasons. Neither of these apply to EXC_RESOURCE or EXC_GUARD.
142   // See 10.10 xnu-2782.1.97/bsd/kern/kern_exit.c proc_prepareexit(). Receiving
143   // these exception types wrapped in EXC_CRASH would lose information because
144   // their code[0] uses all 64 bits (see ExceptionSnapshotMac::Initialize()) and
145   // the code[0] recovered from EXC_CRASH only contains 20 significant bits.
146   //
147   // EXC_CORPSE_NOTIFY may be generated from EXC_CRASH, but the opposite should
148   // never occur.
149   //
150   // kMachExceptionSimulated is a non-fatal Crashpad-specific pseudo-exception
151   // that never exists as an exception within the kernel and should thus never
152   // be wrapped in EXC_CRASH.
153   return exception != EXC_CRASH &&
154          exception != EXC_RESOURCE &&
155          exception != EXC_GUARD &&
156          exception != EXC_CORPSE_NOTIFY &&
157          exception != kMachExceptionSimulated;
158 }
159 
ExceptionCodeForMetrics(exception_type_t exception,mach_exception_code_t code_0)160 int32_t ExceptionCodeForMetrics(exception_type_t exception,
161                                 mach_exception_code_t code_0) {
162   if (exception == kMachExceptionSimulated) {
163     return exception;
164   }
165 
166   int signal = 0;
167   if (exception == EXC_CRASH) {
168     const exception_type_t original_exception =
169         ExcCrashRecoverOriginalException(code_0, &code_0, &signal);
170     if (!ExcCrashCouldContainException(original_exception)) {
171       LOG(WARNING) << "EXC_CRASH should not contain exception "
172                    << original_exception;
173       return InRangeCast<uint16_t>(original_exception, 0xffff) << 16;
174     }
175     exception = original_exception;
176   }
177 
178   uint16_t metrics_exception = InRangeCast<uint16_t>(exception, 0xffff);
179 
180   uint16_t metrics_code_0;
181   switch (exception) {
182     case EXC_RESOURCE:
183       metrics_code_0 = (EXC_RESOURCE_DECODE_RESOURCE_TYPE(code_0) << 8) |
184                        EXC_RESOURCE_DECODE_FLAVOR(code_0);
185       break;
186 
187     case EXC_GUARD: {
188       // This will be GUARD_TYPE_MACH_PORT (1) from <mach/port.h> or
189       // GUARD_TYPE_FD (2) from 10.12.2 xnu-3789.31.2/bsd/sys/guarded.h
190       const uint8_t guard_type = (code_0) >> 61;
191 
192       // These exceptions come through 10.12.2
193       // xnu-3789.31.2/osfmk/ipc/mach_port.c mach_port_guard_exception() or
194       // xnu-3789.31.2/bsd/kern/kern_guarded.c fp_guard_exception(). In each
195       // case, bits 32-60 of code_0 encode the guard type-specific “flavor”. For
196       // Mach port guards, these flavor codes come from the
197       // mach_port_guard_exception_codes enum in <mach/port.h>. For file
198       // descriptor guards, they come from the guard_exception_codes enum in
199       // xnu-3789.31.2/bsd/sys/guarded.h. Both of these enums define shifted-bit
200       // values (1 << 0, 1 << 1, 1 << 2, etc.) In actual usage as determined by
201       // callers to these functions, these “flavor” codes are never ORed with
202       // one another. For the purposes of encoding these codes for metrics,
203       // convert the flavor codes to their corresponding bit shift values.
204       const uint32_t guard_flavor = (code_0 >> 32) & 0x1fffffff;
205       const int first_bit = ffs(guard_flavor);
206       uint8_t metrics_guard_flavor;
207       if (first_bit) {
208         metrics_guard_flavor = first_bit - 1;
209 
210         const uint32_t test_guard_flavor = 1 << metrics_guard_flavor;
211         if (guard_flavor != test_guard_flavor) {
212           // Another bit is set.
213           DCHECK_EQ(guard_flavor, test_guard_flavor);
214           metrics_guard_flavor = 0xff;
215         }
216       } else {
217         metrics_guard_flavor = 0xff;
218       }
219 
220       metrics_code_0 = (guard_type << 8) | metrics_guard_flavor;
221       break;
222     }
223 
224     case EXC_CORPSE_NOTIFY:
225       // code_0 may be a pointer. See 10.12.2 xnu-3789.31.2/osfmk/kern/task.c
226       // task_deliver_crash_notification(). Just encode 0 for metrics purposes.
227       metrics_code_0 = 0;
228       break;
229 
230     default:
231       metrics_code_0 = InRangeCast<uint16_t>(code_0, 0xffff);
232       if (exception == 0 && metrics_code_0 == 0 && signal != 0) {
233         // This exception came from a signal that did not originate as another
234         // Mach exception. Encode the signal number, using EXC_CRASH as the
235         // top-level exception type. This is safe because EXC_CRASH will not
236         // otherwise appear as metrics_exception.
237         metrics_exception = EXC_CRASH;
238         metrics_code_0 = signal;
239       }
240       break;
241   }
242 
243   return (metrics_exception << 16) | metrics_code_0;
244 }
245 
IsExceptionNonfatalResource(exception_type_t exception,mach_exception_code_t code_0,pid_t pid)246 bool IsExceptionNonfatalResource(exception_type_t exception,
247                                  mach_exception_code_t code_0,
248                                  pid_t pid) {
249   if (exception != EXC_RESOURCE) {
250     return false;
251   }
252 
253   const int resource_type = EXC_RESOURCE_DECODE_RESOURCE_TYPE(code_0);
254   const int resource_flavor = EXC_RESOURCE_DECODE_FLAVOR(code_0);
255 
256   if (resource_type == RESOURCE_TYPE_CPU &&
257       (resource_flavor == FLAVOR_CPU_MONITOR ||
258        resource_flavor == FLAVOR_CPU_MONITOR_FATAL)) {
259     // These exceptions may be fatal. They are not fatal by default at task
260     // creation but can be made fatal by calling proc_rlimit_control() with
261     // RLIMIT_CPU_USAGE_MONITOR as the second argument and CPUMON_MAKE_FATAL set
262     // in the flags.
263     if (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_10 ||
264         MacOSVersionNumber() >= 10'10'00) {
265       // In OS X 10.10, the exception code indicates whether the exception is
266       // fatal. See 10.10 xnu-2782.1.97/osfmk/kern/thread.c
267       // THIS_THREAD_IS_CONSUMING_TOO_MUCH_CPU__SENDING_EXC_RESOURCE().
268       return resource_flavor == FLAVOR_CPU_MONITOR;
269     }
270 
271     // In OS X 10.9, there’s no way to determine whether the exception is fatal.
272     // Unlike RESOURCE_TYPE_WAKEUPS below, there’s no way to determine this
273     // outside the kernel. proc_rlimit_control()’s RLIMIT_CPU_USAGE_MONITOR is
274     // the only interface to modify CPUMON_MAKE_FATAL, but it’s only able to set
275     // this bit, not obtain its current value.
276     //
277     // Default to assuming that these exceptions are nonfatal. They are nonfatal
278     // by default and no users of proc_rlimit_control() were found on 10.9.5
279     // 13F1066 in /System and /usr outside of Metadata.framework and associated
280     // tools.
281     return true;
282   }
283 
284   if (resource_type == RESOURCE_TYPE_WAKEUPS &&
285       resource_flavor == FLAVOR_WAKEUPS_MONITOR) {
286     // These exceptions may be fatal. They are not fatal by default at task
287     // creation, but can be made fatal by calling proc_rlimit_control() with
288     // RLIMIT_WAKEUPS_MONITOR as the second argument and WAKEMON_MAKE_FATAL set
289     // in the flags.
290     //
291     // proc_get_wakemon_params() (which calls
292     // through to proc_rlimit_control() with RLIMIT_WAKEUPS_MONITOR) determines
293     // whether these exceptions are fatal. See 10.10
294     // xnu-2782.1.97/osfmk/kern/task.c
295     // THIS_PROCESS_IS_CAUSING_TOO_MANY_WAKEUPS__SENDING_EXC_RESOURCE().
296     //
297     // If proc_get_wakemon_params() fails, default to assuming that these
298     // exceptions are nonfatal. They are nonfatal by default and no users of
299     // proc_rlimit_control() were found on 10.9.5 13F1066 in /System and /usr
300     // outside of Metadata.framework and associated tools.
301     int wm_rate;
302     int wm_flags;
303     int rv = ProcGetWakemonParams(pid, &wm_rate, &wm_flags);
304     if (rv < 0) {
305       PLOG(WARNING) << "ProcGetWakemonParams";
306       return true;
307     }
308 
309     return !(wm_flags & WAKEMON_MAKE_FATAL);
310   }
311 
312   if (resource_type == RESOURCE_TYPE_MEMORY &&
313       resource_flavor == FLAVOR_HIGH_WATERMARK) {
314     // These exceptions were never fatal prior to 10.12. See 10.10
315     // xnu-2782.1.97/osfmk/kern/task.c
316     // THIS_PROCESS_CROSSED_HIGH_WATERMARK__SENDING_EXC_RESOURCE().
317     //
318     // A superficial examination of 10.12 shows that these exceptions may be
319     // fatal, as determined by the P_MEMSTAT_FATAL_MEMLIMIT bit of the
320     // kernel-internal struct proc::p_memstat_state. See 10.12.3
321     // xnu-3789.41.3/osfmk/kern/task.c task_footprint_exceeded(). This bit is
322     // not exposed to user space, which makes it difficult to determine whether
323     // the kernel considers a given instance of this exception fatal. However, a
324     // close read reveals that it is only possible for this bit to become set
325     // when xnu-3789.41.3/bsd/kern/kern_memorystatus.c
326     // memorystatus_cmd_set_memlimit_properties() is called, which is only
327     // possible when the kernel is built with CONFIG_JETSAM set, or if the
328     // kern.memorystatus_highwater_enabled sysctl is used, which is only
329     // possible when the kernel is built with DEVELOPMENT or DEBUG set. Although
330     // CONFIG_JETSAM is used on iOS, it is not used on macOS. DEVELOPMENT and
331     // DEBUG are also not set for production kernels. It therefore remains
332     // impossible for these exceptions to be fatal, even on 10.12.
333     return true;
334   }
335 
336   if (resource_type == RESOURCE_TYPE_IO) {
337     // These exceptions are never fatal. See 10.12.3
338     // xnu-3789.41.3/osfmk/kern/task.c
339     // SENDING_NOTIFICATION__THIS_PROCESS_IS_CAUSING_TOO_MUCH_IO().
340     return true;
341   }
342 
343   // Treat unknown exceptions as fatal. This is the conservative approach: it
344   // may result in more crash reports being generated, but the type-flavor
345   // combinations can be evaluated to determine appropriate handling.
346   LOG(WARNING) << "unknown resource type " << resource_type << " flavor "
347                << resource_flavor;
348   return false;
349 }
350 
351 }  // namespace crashpad
352