crashpad/handler/mac/crash_report_exception_handler.cc

254 lines
10 KiB
C++
Raw Normal View History

// Copyright 2015 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "handler/mac/crash_report_exception_handler.h"
#include <vector>
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_port.h"
#include "base/strings/stringprintf.h"
#include "client/settings.h"
#include "handler/mac/file_limit_annotation.h"
#include "minidump/minidump_file_writer.h"
#include "minidump/minidump_user_extension_stream_data_source.h"
#include "snapshot/crashpad_info_client_options.h"
#include "snapshot/mac/process_snapshot_mac.h"
#include "util/file/file_writer.h"
#include "util/mach/exc_client_variants.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_types.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/scoped_task_suspend.h"
#include "util/mach/symbolic_constants_mach.h"
#include "util/misc/metrics.h"
#include "util/misc/tri_state.h"
#include "util/misc/uuid.h"
namespace crashpad {
CrashReportExceptionHandler::CrashReportExceptionHandler(
CrashReportDatabase* database,
CrashReportUploadThread* upload_thread,
const std::map<std::string, std::string>* process_annotations,
const UserStreamDataSources* user_stream_data_sources)
: database_(database),
upload_thread_(upload_thread),
process_annotations_(process_annotations),
user_stream_data_sources_(user_stream_data_sources) {}
CrashReportExceptionHandler::~CrashReportExceptionHandler() {
}
kern_return_t CrashReportExceptionHandler::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) {
RecordFileLimitAnnotation();
Metrics::ExceptionEncountered();
mac: Report richer exception codes via metrics Previously, only the top-level exception code was reported via the Crashpad.ExceptionCode.Mac histogram. Making this histogram work (https://crbug.com/678720) has revealed that Chrome is triggering EXC_RESOURCE exceptions at a rate in excess of 4x that of ordinary crashes. These exceptions were not previously visible because they are not uploaded unless the system treats them as fatal, which it does not normally do absent an explicit request. In order to learn more about the problem, this change augments the data reported via the Crashpad.ExceptionCode.Mac histogram to report (at least) second-level exception data. This means that we will no longer see just EXC_RESOURCE, but potentially more useful information such as EXC_RESOURCE / RESOURCE_TYPE_IO / FLAVOR_IO_PHYSICAL_WRITES. This also applies to other exception types, so that the majority of crashes currently falling into the EXC_CRASH bucket will now have additional information decoded and will be reported as, for example, EXC_BAD_ACCESS / KERN_INVALID_ADDRESS, EXC_BAD_INSTRUCTION / EXC_I386_INVOP, and EXC_CRASH / SIGABRT. Because the old mechanism was only live (in an “it works” sense) for several days, and the new mechanism does not overlap with histogram values used by the old one, there’s no need to invent a new histogram name. BUG=chromium:684051 Change-Id: Ia0a372b4127f7b3b2e7dbbaac9304cce3b5aadfe Reviewed-on: https://chromium-review.googlesource.com/430933 Reviewed-by: Scott Graham <scottmg@chromium.org>
2017-01-24 10:59:18 -05:00
Metrics::ExceptionCode(ExceptionCodeForMetrics(exception, code[0]));
*destroy_complex_request = true;
// The expected behavior is EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
// but its possible to deal with any exception behavior as long as it
// carries identity information (valid thread and task ports).
if (!ExceptionBehaviorHasIdentity(behavior)) {
LOG(ERROR) << base::StringPrintf(
"unexpected exception behavior %s, rejecting",
ExceptionBehaviorToString(
behavior, kUseFullName | kUnknownIsNumeric | kUseOr).c_str());
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kUnexpectedExceptionBehavior);
return KERN_FAILURE;
} else if (behavior != (EXCEPTION_STATE_IDENTITY | kMachExceptionCodes)) {
LOG(WARNING) << base::StringPrintf(
"unexpected exception behavior %s, proceeding",
ExceptionBehaviorToString(
behavior, kUseFullName | kUnknownIsNumeric | kUseOr).c_str());
}
if (task == mach_task_self()) {
LOG(ERROR) << "cannot suspend myself";
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kFailedDueToSuspendSelf);
return KERN_FAILURE;
}
ScopedTaskSuspend suspend(task);
ProcessSnapshotMac process_snapshot;
if (!process_snapshot.Initialize(task)) {
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSnapshotFailed);
return KERN_FAILURE;
}
// Check for suspicious message sources. A suspicious exception message comes
// from a source other than the kernel or the process that the exception
// purportedly occurred in.
//
// TODO(mark): Consider exceptions outside of the range (0, 32) from the
// kernel to be suspicious, and exceptions other than kMachExceptionSimulated
// from the process itself to be suspicious.
const pid_t pid = process_snapshot.ProcessID();
pid_t audit_pid = AuditPIDFromMachMessageTrailer(trailer);
if (audit_pid != -1 && audit_pid != 0) {
if (audit_pid != pid) {
LOG(WARNING) << "exception for pid " << pid << " sent by pid "
<< audit_pid;
}
}
CrashpadInfoClientOptions client_options;
process_snapshot.GetCrashpadOptions(&client_options);
mac: Revise incorrect comments about EXC_RESOURCE and EXC_GUARD launchd actually does set the EXC_RESOURCE and EXC_GUARD handlers exactly the same way that it sets the EXC_CRASH handler. See 10.9.5 launchd-842.92.1/src/core.c job_setup_exception_port(). Cases where an EXC_CRASH handler is set but EXC_RESOURCE and EXC_GUARD handlers are not set occur when the exception ports are set by /usr/bin/login instead of launchd. login looks up the exception-reporting service by name and sets the exception port without including EXC_MASK_RESOURCE or EXC_MASK_GUARD in the mask. See 10.10.5 system_cmds-643.30.1/login.tproj/login.c main(). login is a setuid executable, so it does not inherit its parent process’ exception handlers. See 10.10.5 xnu-2782.40.9/osfmk/kern/ipc_tt.c ipc_task_reset(). Terminal.app executes login when establishing its command-line environment, so the exception handlers set for Terminal.app itself (including EXC_MASK_CRASH, EXC_MASK_RESOURCE, and EXC_MASK_GUARD) are discarded, and then login sets an exception handler only for EXC_MASK_CRASH. The same thing occurs for any other process descended from login, including SSH sessions, because sshd executes login. This is a bug in login filed as Apple radar 22978644. This bug led to a misunderstanding about the use of EXC_RESOURCE and EXC_GUARD. Comments that discuss this behavior are now reworded to be accurate, and non-fatal EXC_RESOURCE exceptions are made eligible for forwarding to the user ReportCrash (because it would normally handle them in the absence of Crashpad) while Crashpad itself will still skip processing them. R=rsesek@chromium.org Review URL: https://codereview.chromium.org/1391453002 .
2015-10-05 17:09:45 -04:00
if (client_options.crashpad_handler_behavior != TriState::kDisabled &&
!IsExceptionNonfatalResource(exception, code[0], pid)) {
// Non-fatal resource exceptions are never user-visible and are not
// currently of interest to Crashpad.
if (!process_snapshot.InitializeException(behavior,
thread,
exception,
code,
code_count,
*flavor,
old_state,
old_state_count)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kExceptionInitializationFailed);
return KERN_FAILURE;
}
UUID client_id;
Settings* const settings = database_->GetSettings();
if (settings) {
// If GetSettings() or GetClientID() fails, something else will log a
// message and client_id will be left at its default value, all zeroes,
// which is appropriate.
settings->GetClientID(&client_id);
}
process_snapshot.SetClientID(client_id);
process_snapshot.SetAnnotationsSimpleMap(*process_annotations_);
CrashReportDatabase::NewReport* new_report;
CrashReportDatabase::OperationStatus database_status =
database_->PrepareNewCrashReport(&new_report);
if (database_status != CrashReportDatabase::kNoError) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kPrepareNewCrashReportFailed);
return KERN_FAILURE;
}
process_snapshot.SetReportID(new_report->uuid);
CrashReportDatabase::CallErrorWritingCrashReport
call_error_writing_crash_report(database_, new_report);
WeakFileHandleFileWriter file_writer(new_report->handle);
MinidumpFileWriter minidump;
minidump.InitializeFromSnapshot(&process_snapshot);
AddUserExtensionStreams(
user_stream_data_sources_, &process_snapshot, &minidump);
if (!minidump.WriteEverything(&file_writer)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kMinidumpWriteFailed);
return KERN_FAILURE;
}
call_error_writing_crash_report.Disarm();
UUID uuid;
database_status = database_->FinishedWritingCrashReport(new_report, &uuid);
if (database_status != CrashReportDatabase::kNoError) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
return KERN_FAILURE;
}
upload_thread_->ReportPending(uuid);
}
if (client_options.system_crash_reporter_forwarding != TriState::kDisabled &&
(exception == EXC_CRASH ||
exception == EXC_RESOURCE ||
exception == EXC_GUARD)) {
// Dont forward simulated exceptions such as kMachExceptionSimulated to the
// system crash reporter. Only forward the types of exceptions that it would
// receive under normal conditions. Although the system crash reporter is
// able to deal with other exceptions including simulated ones, forwarding
// them to the system crash reporter could present the systems crash UI for
// processes that havent actually crashed, and could result in reports not
// actually associated with crashes being sent to the operating system
// vendor.
base::mac::ScopedMachSendRight
system_crash_reporter_handler(SystemCrashReporterHandler());
if (system_crash_reporter_handler.get()) {
// Make copies of mutable out parameters so that the system crash reporter
// cant influence the state returned by this method.
thread_state_flavor_t flavor_forward = *flavor;
mach_msg_type_number_t new_state_forward_count = *new_state_count;
std::vector<natural_t> new_state_forward(
new_state, new_state + new_state_forward_count);
// The system crash reporter requires the behavior to be
// EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES. It uses the identity
// parameters but doesnt appear to use the state parameters, including
// |flavor|, and doesnt care if they are 0 or invalid. As long as an
// identity is available (checked above), any other exception behavior is
// converted to what the system crash reporter wants, with the caveat that
// problems may arise if the state wasnt available and the system crash
// reporter changes in the future to use it. However, normally, the state
// will be available.
kern_return_t kr = UniversalExceptionRaise(
EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES,
system_crash_reporter_handler.get(),
thread,
task,
exception,
code,
code_count,
&flavor_forward,
old_state,
old_state_count,
new_state_forward_count ? &new_state_forward[0] : nullptr,
&new_state_forward_count);
MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr) << "UniversalExceptionRaise";
}
}
ExcServerCopyState(
behavior, old_state, old_state_count, new_state, new_state_count);
Metrics::ExceptionCaptureResult(Metrics::CaptureResult::kSuccess);
return ExcServerSuccessfulReturnValue(exception, behavior, false);
}
} // namespace crashpad