Skip to content

Commit ef2a4d8

Browse files
Mac: ensure crashes are forwarded to system crash reporter
Historically, we have forwarded exceptions from Crashpad to Apple's system reporting tool explicitly. However, starting in 10.15, the crash reporter stopped paying attention to EXC_CRASH type exceptions. Instead, it now listens to EXC_CORPSE_NOTIFY, which is raised postmortem if and only if an EXC_CRASH handler returns MACH_RCV_PORT_DIED. But we specifically were *not* doing that in order to avoid multiple reports (one from EXC_CRASH and one from EXC_CORPSE_NOTIFY) between 10.11 and 10.14. This change stops forwarding EXC_CRASH exceptions explicitly and *does* return MACH_RCV_PORT_DIED if they should be forwarded Bug: chromium:388545119 Change-Id: I36a781390386b9e164072fcc602809723a2dc2ee Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/6242575 Reviewed-by: Mark Mentovai <[email protected]>
1 parent 8d6cc37 commit ef2a4d8

File tree

5 files changed

+99
-71
lines changed

5 files changed

+99
-71
lines changed

client/crashpad_client_mac.cc

+15-8
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ std::string FormatArgumentInt(const std::string& name, int value) {
6464
// reasons, an EXC_CRASH exception will be sent. See 10.9.5
6565
// xnu-2422.115.4/bsd/kern/kern_exit.c proc_prepareexit().
6666
//
67-
// EXC_RESOURCE and EXC_GUARD do not become signals or EXC_CRASH exceptions. The
68-
// host-level exception handler in the kernel does not receive these exception
69-
// types, and even if it did, it would not map them to signals. Instead, the
70-
// first Mach service loaded by the root (process ID 1) launchd with a boolean
71-
// “ExceptionServer” property in its job dictionary (regardless of its value) or
72-
// with any subdictionary property will become the host-level exception handler
73-
// for EXC_CRASH, EXC_RESOURCE, and EXC_GUARD. See 10.9.5
67+
// EXC_RESOURCE and EXC_GUARD (pre-macOS 13) do not become signals or EXC_CRASH
68+
// exceptions. The host-level exception handler in the kernel does not receive
69+
// these exception types, and even if it did, it would not map them to signals.
70+
// Instead, the first Mach service loaded by the root (process ID 1) launchd
71+
// with a boolean “ExceptionServer” property in its job dictionary (regardless
72+
// of its value) or with any subdictionary property will become the host-level
73+
// exception handler for EXC_CRASH, EXC_RESOURCE, and EXC_GUARD. See 10.9.5
7474
// launchd-842.92.1/src/core.c job_setup_exception_port(). Normally, this job is
7575
// com.apple.ReportCrash.Root, the systemwide Apple Crash Reporter. Since it is
7676
// impossible to receive EXC_RESOURCE and EXC_GUARD exceptions through the
@@ -83,8 +83,15 @@ std::string FormatArgumentInt(const std::string& name, int value) {
8383
// so AND them with ExcMaskValid(). EXC_MASK_CRASH is always supported.
8484
bool SetCrashExceptionPorts(exception_handler_t exception_handler) {
8585
ExceptionPorts exception_ports(ExceptionPorts::kTargetTypeTask, TASK_NULL);
86+
87+
exception_mask_t mask = EXC_MASK_CRASH | EXC_MASK_RESOURCE;
88+
if (MacOSVersionNumber() < 13'00'00) {
89+
// EXC_GUARD is delivered as an EXC_CRASH macOS 13 and later.
90+
mask |= EXC_MASK_GUARD;
91+
}
92+
8693
return exception_ports.SetExceptionPort(
87-
(EXC_MASK_CRASH | EXC_MASK_RESOURCE | EXC_MASK_GUARD) & ExcMaskValid(),
94+
mask & ExcMaskValid(),
8895
exception_handler,
8996
EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
9097
MACHINE_THREAD_STATE);

handler/mac/crash_report_exception_handler.cc

+67-46
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "util/mach/bootstrap.h"
3232
#include "util/mach/exc_client_variants.h"
3333
#include "util/mach/exception_behaviors.h"
34+
#include "util/mach/exception_ports.h"
3435
#include "util/mach/exception_types.h"
3536
#include "util/mach/mach_extensions.h"
3637
#include "util/mach/mach_message.h"
@@ -188,59 +189,79 @@ kern_return_t CrashReportExceptionHandler::CatchMachException(
188189
}
189190
}
190191

191-
if (client_options.system_crash_reporter_forwarding != TriState::kDisabled &&
192-
(exception == EXC_CRASH ||
193-
exception == EXC_RESOURCE ||
194-
exception == EXC_GUARD)) {
195-
// Don’t forward simulated exceptions such as kMachExceptionSimulated to the
196-
// system crash reporter. Only forward the types of exceptions that it would
197-
// receive under normal conditions. Although the system crash reporter is
198-
// able to deal with other exceptions including simulated ones, forwarding
199-
// them to the system crash reporter could present the system’s crash UI for
200-
// processes that haven’t actually crashed, and could result in reports not
201-
// actually associated with crashes being sent to the operating system
202-
// vendor.
203-
base::apple::ScopedMachSendRight system_crash_reporter_handler(
204-
SystemCrashReporterHandler());
205-
if (system_crash_reporter_handler.get()) {
206-
// Make copies of mutable out parameters so that the system crash reporter
207-
// can’t influence the state returned by this method.
208-
thread_state_flavor_t flavor_forward = *flavor;
209-
mach_msg_type_number_t new_state_forward_count = *new_state_count;
210-
std::vector<natural_t> new_state_forward(
211-
new_state, new_state + new_state_forward_count);
212-
213-
// The system crash reporter requires the behavior to be
214-
// EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES. It uses the identity
215-
// parameters but doesn’t appear to use the state parameters, including
216-
// |flavor|, and doesn’t care if they are 0 or invalid. As long as an
217-
// identity is available (checked above), any other exception behavior is
218-
// converted to what the system crash reporter wants, with the caveat that
219-
// problems may arise if the state wasn’t available and the system crash
220-
// reporter changes in the future to use it. However, normally, the state
221-
// will be available.
222-
kern_return_t kr = UniversalExceptionRaise(
223-
EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
224-
system_crash_reporter_handler.get(),
225-
thread,
226-
task,
227-
exception,
228-
code,
229-
code_count,
230-
&flavor_forward,
231-
old_state,
232-
old_state_count,
233-
new_state_forward_count ? &new_state_forward[0] : nullptr,
234-
&new_state_forward_count);
235-
MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr) << "UniversalExceptionRaise";
192+
if (client_options.system_crash_reporter_forwarding != TriState::kDisabled) {
193+
if (exception == EXC_CRASH) {
194+
// For exception handlers that respond to state-carrying behaviors, when
195+
// the handler is called by the kernel (as it is normally), the kernel
196+
// will attempt to set a new thread state when the exception handler
197+
// returns successfully. Other code that mimics the kernel’s
198+
// exception-delivery semantics may implement the same or similar
199+
// behavior. In some situations, it is undesirable to set a new thread
200+
// state. If the exception handler were to return unsuccessfully, however,
201+
// the kernel would continue searching for an exception handler at a wider
202+
// (task or host) scope. This may also be undesirable.
203+
//
204+
// If such exception handlers return `MACH_RCV_PORT_DIED`, the kernel will
205+
// not set a new thread state and will also not search for another
206+
// exception handler. See 15.3 xnu-11215.84.4/osfmk/kern/exception.c.
207+
// `exception_deliver()` will only set a new thread state if the handler’s
208+
// return code was `MACH_MSG_SUCCESS` (a synonym for `KERN_SUCCESS`), and
209+
// subsequently, `exception_triage()` will not search for a new handler if
210+
// the handler’s return code was `KERN_SUCCESS` or `MACH_RCV_PORT_DIED`.
211+
//
212+
// Another effect of returning `MACH_RCV_PORT_DIED` for `EXC_CRASH` is
213+
// that an `EXC_CORPSE_NOTIFY` exception is generated. Starting with macOS
214+
// 10.15, for the system crash reporter to generate a report,
215+
// `EXC_CORPSE_NOTIFY` *must* be generated and forwarding `EXC_CRASH` (as
216+
// we do below with `EXC_RESOURCE` and pre-macOS 13 `EXC_GUARD`) is not
217+
// sufficient. Between macOS 10.11 and macOS 10.14 (inclusive), both
218+
// forwarding as below, and causing `EXC_CORPSE_NOTIFY` to be generated
219+
// are sufficient (and in fact, if we do both, two crash reports are
220+
// generated).
221+
return MACH_RCV_PORT_DIED;
222+
}
223+
if (exception == EXC_RESOURCE || exception == EXC_GUARD) {
224+
// Only forward the types of exceptions that the crash reporter would
225+
// receive under normal conditions. Otherwise, system crash reporter could
226+
// present the system’s crash UI for processes that haven’t actually
227+
// crashed, and could result in reports not actually associated with
228+
// crashes being sent to the operating system vendor.
229+
base::apple::ScopedMachSendRight system_crash_reporter_handler(
230+
SystemCrashReporterHandler());
231+
if (system_crash_reporter_handler.get()) {
232+
// Make copies of mutable out parameters so that the system crash
233+
// reporter can’t influence the state returned by this method.
234+
thread_state_flavor_t flavor_forward = *flavor;
235+
mach_msg_type_number_t new_state_forward_count = *new_state_count;
236+
std::vector<natural_t> new_state_forward;
237+
if (new_state_forward_count) {
238+
new_state_forward.assign(new_state,
239+
new_state + new_state_forward_count);
240+
}
241+
kern_return_t kr = UniversalExceptionRaise(
242+
EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
243+
system_crash_reporter_handler.get(),
244+
thread,
245+
task,
246+
exception,
247+
code,
248+
code_count,
249+
&flavor_forward,
250+
old_state,
251+
old_state_count,
252+
new_state_forward_count ? &new_state_forward[0] : nullptr,
253+
&new_state_forward_count);
254+
MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr)
255+
<< "UniversalExceptionRaise";
256+
}
236257
}
237258
}
238259

239260
ExcServerCopyState(
240261
behavior, old_state, old_state_count, new_state, new_state_count);
241262

242263
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess);
243-
return ExcServerSuccessfulReturnValue(exception, behavior, false);
264+
return KERN_SUCCESS;
244265
}
245266

246267
} // namespace crashpad

tools/run_with_crashpad.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ along with any arguments specified (_ARG…_) with the new exception port in
3333
effect.
3434

3535
On macOS, the exception port is configured to receive exceptions of type
36-
`EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD`. The exception behavior is
37-
configured as `EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES`. The thread
38-
state flavor is set to `MACHINE_THREAD_STATE`.
36+
`EXC_CRASH`, `EXC_RESOURCE`, and `EXC_GUARD` (until macOS 13). The exception
37+
behavior is configured as `EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES`. The
38+
thread state flavor is set to `MACHINE_THREAD_STATE`.
3939

4040
Programs that use the Crashpad client library directly will not normally use
4141
this tool. This tool exists to allow programs that are unaware of Crashpad to be

util/mach/exception_types.cc

+14-13
Original file line numberDiff line numberDiff line change
@@ -135,26 +135,27 @@ exception_type_t ExcCrashRecoverOriginalException(
135135
bool ExcCrashCouldContainException(exception_type_t exception) {
136136
// EXC_CRASH should never be wrapped in another EXC_CRASH.
137137
//
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.
138+
// EXC_RESOURCE is a software exceptions that is never wrapped in EXC_CRASH.
139+
// The same applies to EXC_GUARD below macOS 13. The only time EXC_CRASH is
140+
// generated is for processes exiting due to an unhandled core-generating
141+
// signal or being killed by SIGKILL for code-signing reasons. Neither of
142+
// these apply to EXC_RESOURCE or EXC_GUARD. See 10.10
143+
// xnu-2782.1.97/bsd/kern/kern_exit.c proc_prepareexit(). Receiving these
144+
// exception types wrapped in EXC_CRASH would lose information because their
145+
// code[0] uses all 64 bits (see ExceptionSnapshotMac::Initialize()) and the
146+
// code[0] recovered from EXC_CRASH only contains 20 significant bits.
146147
//
147148
// EXC_CORPSE_NOTIFY may be generated from EXC_CRASH, but the opposite should
148149
// never occur.
149150
//
150151
// kMachExceptionSimulated is a non-fatal Crashpad-specific pseudo-exception
151152
// that never exists as an exception within the kernel and should thus never
152153
// 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;
154+
if (MacOSVersionNumber() < 13'00'00 && exception == EXC_GUARD) {
155+
return false;
156+
}
157+
return exception != EXC_CRASH && exception != EXC_RESOURCE &&
158+
exception != EXC_CORPSE_NOTIFY && exception != kMachExceptionSimulated;
158159
}
159160

160161
int32_t ExceptionCodeForMetrics(exception_type_t exception,

util/mach/exception_types_test.cc

-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ TEST(ExceptionTypes, ExcCrashCouldContainException) {
124124
EXPECT_TRUE(ExcCrashCouldContainException(EXC_RPC_ALERT));
125125
EXPECT_FALSE(ExcCrashCouldContainException(EXC_CRASH));
126126
EXPECT_FALSE(ExcCrashCouldContainException(EXC_RESOURCE));
127-
EXPECT_FALSE(ExcCrashCouldContainException(EXC_GUARD));
128127
EXPECT_FALSE(ExcCrashCouldContainException(EXC_CORPSE_NOTIFY));
129128
EXPECT_FALSE(ExcCrashCouldContainException(kMachExceptionSimulated));
130129
}

0 commit comments

Comments
 (0)