blob: 010ce9892828c0ebc4824721ba6baa82107bf362 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "crash-reporter/user_collector.h"
#include <bits/wordsize.h>
#include <elf.h>
#include <fcntl.h>
#include <stdint.h>
#include <unordered_set>
#include <utility>
#include <base/check.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/containers/contains.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/process/process.h>
#include "crash-reporter/user_collector_base.h"
#include "crash-reporter/util.h"
#include "crash-reporter/vm_support.h"
using base::FilePath;
using base::StringPrintf;
namespace {
// This procfs file is used to cause kernel core file writing to
// instead pipe the core file into a user space process. See
// core(5) man page.
const char kCorePatternFile[] = "/proc/sys/kernel/core_pattern";
const char kCorePipeLimitFile[] = "/proc/sys/kernel/core_pipe_limit";
// Set core_pipe_limit to 4 so that we can catch a few unrelated concurrent
// crashes, but finite to avoid infinitely recursing on crash handling.
const char kCorePipeLimit[] = "4";
const char kCoreToMinidumpConverterPath[] = "/usr/bin/core2md";
const char kFilterPath[] = "/opt/google/crash-reporter/filter";
// Core pattern lock file: only exists on linux-3.18 and earlier.
const char kCorePatternLockFile[] = "/proc/sys/kernel/lock_core_pattern";
// Filename we touch in our state directory when we get enabled.
constexpr char kCrashHandlingEnabledFlagFile[] = "crash-handling-enabled";
// Returns true if the given executable name matches that of Chrome. This
// includes checks for threads that Chrome has renamed.
bool IsChromeExecName(const std::string& exec);
// This is needed for kernels older than linux-4.4. Once we drop support for
// older kernels (upgrading or going EOL), we can drop this logic.
bool LockCorePattern() {
base::FilePath core_pattern_lock_file(kCorePatternLockFile);
// Core pattern lock was only added for kernel versions before 4.4.
if (!base::PathExists(core_pattern_lock_file)) {
VLOG(1) << "No core pattern lock available";
return true;
}
if (util::IsDeveloperImage()) {
LOG(INFO) << "Developer image -- leaving core pattern unlocked";
return true;
}
if (base::WriteFile(core_pattern_lock_file, "1", 1) != 1) {
PLOG(ERROR) << "Failed to lock core pattern";
return false;
}
return true;
}
} // namespace
UserCollector::UserCollector()
: UserCollectorBase("user", kUseNormalCrashDirectorySelectionMethod),
core_pattern_file_(kCorePatternFile),
core_pipe_limit_file_(kCorePipeLimitFile),
filter_path_(kFilterPath),
core2md_failure_(false) {}
void UserCollector::Initialize(
const std::string& our_path,
bool core2md_failure,
bool directory_failure,
bool early) {
UserCollectorBase::Initialize(directory_failure, early);
our_path_ = our_path;
core2md_failure_ = core2md_failure;
}
UserCollector::~UserCollector() {}
void UserCollector::FinishCrash(const base::FilePath& meta_path,
const std::string& exec_name,
const std::string& payload_name) {
VmSupport* vm_support = VmSupport::Get();
if (vm_support)
vm_support->AddMetadata(this);
UserCollectorBase::FinishCrash(meta_path, exec_name, payload_name);
if (vm_support)
vm_support->FinishCrash(meta_path);
}
// Return the string that should be used for the kernel's core_pattern file.
// Note that if you change the format of the enabled pattern, you'll probably
// also need to change the UserCollectorBase::ParseCrashAttributes function, the
// user_collector_test.cc unittest, the logging_UserCrash.py autotest,
// and the platform.UserCrash tast test.
std::string UserCollector::GetPattern(bool enabled, bool early) const {
if (enabled) {
// Combine the crash attributes into one parameter to try to reduce
// the size of the invocation line for crash_reporter, since the kernel
// has a fixed-sized (128B) buffer for it (before parameter expansion).
// Note that the kernel does not support quoted arguments in core_pattern.
return StringPrintf("|%s %s--user=%%P:%%s:%%u:%%g:%%f", our_path_.c_str(),
early ? "--early --log_to_stderr " : "");
} else {
return "core";
}
}
bool UserCollector::SetUpInternal(bool enabled, bool early) {
CHECK(initialized_);
LOG(INFO) << (enabled ? "Enabling" : "Disabling") << " user crash handling";
if (base::WriteFile(FilePath(core_pipe_limit_file_), kCorePipeLimit,
strlen(kCorePipeLimit)) !=
static_cast<int>(strlen(kCorePipeLimit))) {
PLOG(ERROR) << "Unable to write " << core_pipe_limit_file_;
return false;
}
std::string pattern = GetPattern(enabled, early);
if (base::WriteFile(FilePath(core_pattern_file_), pattern.c_str(),
pattern.length()) != static_cast<int>(pattern.length())) {
int saved_errno = errno;
// If the core pattern is locked and we try to reset the |core_pattern|
// while disabling |user_collector| or resetting it to what it already was,
// expect failure here with an EPERM.
bool ignore_error = false;
if (errno == EPERM && base::PathExists(FilePath(kCorePatternLockFile))) {
std::string actual_contents;
if (!base::ReadFileToString(FilePath(core_pattern_file_),
&actual_contents)) {
PLOG(ERROR) << "Failed to read " << core_pattern_file_;
actual_contents.clear();
}
if (!enabled || base::TrimWhitespaceASCII(
actual_contents, base::TRIM_TRAILING) == pattern) {
ignore_error = true;
LOG(WARNING) << "Failed to write to locked core pattern; ignoring";
}
}
if (!ignore_error) {
LOG(ERROR) << "Unable to write " << core_pattern_file_ << ": "
<< strerror(saved_errno);
return false;
}
}
// Attempt to lock down |core_pattern|: this only works for kernels older than
// linux-3.18.
if (enabled && !early && !LockCorePattern()) {
LOG(ERROR) << "Failed to lock core pattern on a supported device";
return false;
}
// Set up the base crash processing dir for future users.
const FilePath dir = GetCrashProcessingDir();
// First nuke all existing content. This will take care of deleting any
// existing paths (files, symlinks, dirs, etc...) for us.
if (!base::DeletePathRecursively(dir))
PLOG(WARNING) << "Cleanup of directory failed: " << dir.value();
// This will create the directory with 0700 mode. Since init is run as root,
// root will own these too.
if (!base::CreateDirectory(dir)) {
PLOG(ERROR) << "Creating directory failed: " << dir.value();
return false;
}
// Write out a flag file for testing to indicate we have started correctly.
char data[] = "enabled";
size_t write_len = sizeof(data) - 1;
if (base::WriteFile(base::FilePath(crash_reporter_state_path_)
.Append(kCrashHandlingEnabledFlagFile),
data, write_len) != write_len) {
PLOG(WARNING) << "Unable to create flag file for crash reporter enabled";
}
return true;
}
bool UserCollector::CopyOffProcFiles(pid_t pid, const FilePath& container_dir) {
FilePath process_path = GetProcessPath(pid);
if (!base::PathExists(process_path)) {
LOG(ERROR) << "Path " << process_path.value() << " does not exist";
return false;
}
// NB: We can't (yet) use brillo::SafeFD here because it does not support
// reading /proc files, due to the fact that they report 0 size.
// TODO(b/195787611): Use SafeFD.
int processpath_fd;
if (!ValidatePathAndOpen(process_path, &processpath_fd)) {
LOG(ERROR) << "Failed to open process path dir: " << process_path.value();
return false;
}
base::ScopedFD scoped_processpath_fd(processpath_fd);
int containerpath_fd;
if (!ValidatePathAndOpen(container_dir, &containerpath_fd)) {
LOG(ERROR) << "Failed to open container dir:" << container_dir.value();
return false;
}
base::ScopedFD scoped_containerpath_fd(containerpath_fd);
static const char* const kProcFiles[] = {"auxv", "cmdline", "environ", "maps",
"status"};
for (const auto& proc_file : kProcFiles) {
int source_fd = HANDLE_EINTR(
openat(processpath_fd, proc_file, O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
if (source_fd < 0) {
PLOG(ERROR) << "Failed to open " << process_path << "/" << proc_file;
return false;
}
base::File source(source_fd);
int dest_fd = HANDLE_EINTR(
openat(containerpath_fd, proc_file,
O_CREAT | O_WRONLY | O_TRUNC | O_EXCL | O_NOFOLLOW | O_CLOEXEC,
kSystemCrashFilesMode));
if (dest_fd < 0) {
PLOG(ERROR) << "Failed to open " << container_dir << "/" << proc_file;
return false;
}
base::File dest(dest_fd);
if (!base::CopyFileContents(source, dest)) {
LOG(ERROR) << "Failed to copy " << proc_file;
return false;
}
}
return true;
}
bool UserCollector::ValidateProcFiles(const FilePath& container_dir) const {
// Check if the maps file is empty, which could be due to the crashed
// process being reaped by the kernel before finishing a core dump.
int64_t file_size = 0;
if (!base::GetFileSize(container_dir.Append("maps"), &file_size)) {
PLOG(ERROR) << "Could not get the size of maps file";
return false;
}
if (file_size == 0) {
LOG(ERROR) << "maps file is empty";
return false;
}
return true;
}
UserCollector::ErrorType UserCollector::ValidateCoreFile(
const FilePath& core_path) const {
int fd = HANDLE_EINTR(open(core_path.value().c_str(), O_RDONLY));
if (fd < 0) {
PLOG(ERROR) << "Could not open core file " << core_path.value();
return kErrorReadCoreData;
}
char e_ident[EI_NIDENT];
bool read_ok = base::ReadFromFD(fd, e_ident, sizeof(e_ident));
IGNORE_EINTR(close(fd));
if (!read_ok) {
LOG(ERROR) << "Could not read header of core file";
return kErrorInvalidCoreFile;
}
if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1 ||
e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3) {
LOG(ERROR) << "Invalid core file";
return kErrorInvalidCoreFile;
}
#if __WORDSIZE == 64
// TODO(benchan, mkrebs): Remove this check once core2md can
// handles both 32-bit and 64-bit ELF on a 64-bit platform.
if (e_ident[EI_CLASS] == ELFCLASS32) {
LOG(ERROR) << "Conversion of 32-bit core file on 64-bit platform is "
<< "currently not supported";
return kErrorUnsupported32BitCoreFile;
}
#endif
return kErrorNone;
}
// Copy off all stdin to a core file.
bool UserCollector::CopyStdinToCoreFile(const FilePath& core_path) {
// We need to write to an actual file here for core2md.
// If we're in memfd mode, fail out.
if (crash_sending_mode_ == kCrashLoopSendingMode) {
LOG(ERROR) << "Cannot call CopyFdToNewFile in kCrashLoopSendingMode";
return false;
}
// We don't directly create a ScopedFD with STDIN_FILENO because the
// destructor would close() that file descriptor, and we don't want to close
// stdin.
base::ScopedFD stdin_copy(dup(STDIN_FILENO));
if (!stdin_copy.is_valid()) {
return false;
}
if (CopyFdToNewFile(std::move(stdin_copy), core_path)) {
return true;
}
PLOG(ERROR) << "Could not write core file";
// If the file system was full, make sure we remove any remnants.
base::DeleteFile(core_path);
return false;
}
bool UserCollector::RunCoreToMinidump(const FilePath& core_path,
const FilePath& procfs_directory,
const FilePath& minidump_path,
const FilePath& temp_directory) {
FilePath output_path = temp_directory.Append("output");
brillo::ProcessImpl core2md;
core2md.RedirectOutput(output_path.value());
core2md.AddArg(kCoreToMinidumpConverterPath);
core2md.AddArg(core_path.value());
core2md.AddArg(procfs_directory.value());
if (!core2md_failure_) {
core2md.AddArg(minidump_path.value());
} else {
// To test how core2md errors are propagaged, cause an error
// by forgetting a required argument.
}
int errorlevel = core2md.Run();
std::string output;
base::ReadFileToString(output_path, &output);
if (errorlevel != 0) {
LOG(ERROR) << "Problem during " << kCoreToMinidumpConverterPath
<< " [result=" << errorlevel << "]: " << output;
return false;
}
// Change the minidump to be not-world-readable. chmod will change permissions
// on symlinks. Use fchmod instead.
base::ScopedFD minidump(
open(minidump_path.value().c_str(), O_RDONLY | O_NOFOLLOW | O_CLOEXEC));
if (!minidump.is_valid()) {
PLOG(ERROR) << "Could not open minidump file: " << minidump_path.value();
return false;
}
if (fchmod(minidump.get(), kSystemCrashFilesMode) < 0) {
PLOG(ERROR) << "Couldn't chmod minidump file: " << minidump_path.value();
return false;
}
return true;
}
bool UserCollector::RunFilter(pid_t pid) {
int mode;
int exec_mode = base::FILE_PERMISSION_EXECUTE_BY_USER |
base::FILE_PERMISSION_EXECUTE_BY_GROUP |
base::FILE_PERMISSION_EXECUTE_BY_OTHERS;
if (!base::GetPosixFilePermissions(base::FilePath(filter_path_), &mode) ||
(mode & exec_mode) != exec_mode) {
// Filter does not exist or is not executable.
return true;
}
brillo::ProcessImpl filter;
filter.AddArg(filter_path_);
filter.AddArg(StringPrintf("%d", pid));
return filter.Run() == 0;
}
bool UserCollector::ShouldDump(pid_t pid,
bool handle_chrome_crashes,
const std::string& exec,
std::string* reason) {
reason->clear();
// Treat Chrome crashes as if the user opted-out. We stop counting Chrome
// crashes towards user crashes, so user crashes really mean non-Chrome
// user-space crashes.
if (!handle_chrome_crashes && IsChromeExecName(exec)) {
// anomaly_detector's CrashReporterParser looks for this message; don't
// change it without updating the regex.
*reason =
"ignoring call by kernel - chrome crash; "
"waiting for chrome to call us directly";
return false;
}
if (!RunFilter(pid)) {
*reason = "filtered out";
return false;
}
return UserCollectorBase::ShouldDump(pid, reason);
}
bool UserCollector::ShouldDump(pid_t pid,
uid_t,
const std::string& exec,
std::string* reason) {
return ShouldDump(pid, ShouldHandleChromeCrashes(), exec, reason);
}
UserCollector::ErrorType UserCollector::ConvertCoreToMinidump(
pid_t pid,
const FilePath& container_dir,
const FilePath& core_path,
const FilePath& minidump_path) {
// If proc files are unusable, we continue to read the core file from stdin,
// but only skip the core-to-minidump conversion, so that we may still use
// the core file for debugging.
bool proc_files_usable =
CopyOffProcFiles(pid, container_dir) && ValidateProcFiles(container_dir);
if (!CopyStdinToCoreFile(core_path)) {
return kErrorReadCoreData;
}
if (!proc_files_usable) {
LOG(INFO) << "Skipped converting core file to minidump due to "
<< "unusable proc files";
return kErrorUnusableProcFiles;
}
ErrorType error = ValidateCoreFile(core_path);
if (error != kErrorNone) {
return error;
}
if (!RunCoreToMinidump(core_path,
container_dir, // procfs directory
minidump_path,
container_dir)) { // temporary directory
return kErrorCore2MinidumpConversion;
}
return kErrorNone;
}
namespace {
bool IsChromeExecName(const std::string& exec) {
static const char* const kChromeNames[] = {
"chrome",
// These are additional thread names seen in http://crash/
"MediaPipeline",
// These come from the use of base::PlatformThread::SetName() directly
"CrBrowserMain", "CrRendererMain", "CrUtilityMain", "CrPPAPIMain",
"CrPPAPIBrokerMain", "CrPluginMain", "CrWorkerMain", "CrGpuMain",
"BrokerEvent", "CrVideoRenderer", "CrShutdownDetector", "UsbEventHandler",
"CrNaClMain", "CrServiceMain",
// These thread names come from the use of base::Thread
"Gamepad polling thread", "Chrome_InProcGpuThread",
"Chrome_DragDropThread", "Renderer::FILE", "VC manager",
"VideoCaptureModuleImpl", "JavaBridge", "VideoCaptureManagerThread",
"Geolocation", "Geolocation_wifi_provider",
"Device orientation polling thread", "Chrome_InProcRendererThread",
"NetworkChangeNotifier", "Watchdog", "inotify_reader",
"cf_iexplore_background_thread", "BrowserWatchdog",
"Chrome_HistoryThread", "Chrome_SyncThread", "Chrome_ShellDialogThread",
"Printing_Worker", "Chrome_SafeBrowsingThread", "SimpleDBThread",
"D-Bus thread", "AudioThread", "NullAudioThread", "V4L2Thread",
"ChromotingClientDecodeThread", "Profiling_Flush", "worker_thread_ticker",
"AudioMixerAlsa", "AudioMixerCras", "FakeAudioRecordingThread",
"CaptureThread", "Chrome_WebSocketproxyThread", "ProcessWatcherThread",
"Chrome_CameraThread", "import_thread", "NaCl_IOThread",
"Chrome_CloudPrintJobPrintThread", "Chrome_CloudPrintProxyCoreThread",
"DaemonControllerFileIO", "ChromotingMainThread",
"ChromotingEncodeThread", "ChromotingDesktopThread", "ChromotingIOThread",
"ChromotingFileIOThread", "Chrome_libJingle_WorkerThread",
"Chrome_ChildIOThread", "GLHelperThread", "RemotingHostPlugin",
// "PAC thread #%d", // not easy to check because of "%d"
"Chrome_DBThread", "Chrome_WebKitThread", "Chrome_FileThread",
"Chrome_FileUserBlockingThread", "Chrome_ProcessLauncherThread",
"Chrome_CacheThread", "Chrome_IOThread", "Cache Thread", "File Thread",
"ServiceProcess_IO", "ServiceProcess_File", "extension_crash_uploader",
"gpu-process_crash_uploader", "plugin_crash_uploader",
"renderer_crash_uploader",
// These come from the use of webkit_glue::WebThreadImpl
"Compositor", "Browser Compositor",
// "WorkerPool/%d", // not easy to check because of "%d"
// These come from the use of base::Watchdog
"Startup watchdog thread Watchdog", "Shutdown watchdog thread Watchdog",
// These come from the use of AudioDeviceThread::Start
"AudioDevice", "AudioInputDevice", "AudioOutputDevice",
// These come from the use of MessageLoopFactory::GetMessageLoop
"GpuVideoDecoder", "RtcVideoDecoderThread", "PipelineThread",
"AudioDecoderThread", "VideoDecoderThread",
// These come from the use of MessageLoopFactory::GetMessageLoopProxy
"CaptureVideoDecoderThread", "CaptureVideoDecoder",
// These come from the use of base::SimpleThread
"LocalInputMonitor/%d", // "%d" gets lopped off for kernel-supplied
// These come from the use of base::DelegateSimpleThread
"ipc_channel_nacl reader thread/%d", "plugin_audio_input_thread/%d",
"plugin_audio_thread/%d",
// These come from the use of base::SequencedWorkerPool
"BrowserBlockingWorker%d/%d", // "%d" gets lopped off for kernel-supplied
};
static std::unordered_set<std::string> chrome_names;
// Initialize a set of chrome names, for efficient lookup
if (chrome_names.empty()) {
for (std::string check_name : kChromeNames) {
chrome_names.insert(check_name);
// When checking a kernel-supplied name, it should be truncated to 15
// chars. See PR_SET_NAME in
// http://www.kernel.org/doc/man-pages/online/pages/man2/prctl.2.html,
// although that page misleads by saying "16 bytes".
chrome_names.insert("supplied_" + std::string(check_name, 0, 15));
}
}
return base::Contains(chrome_names, exec);
}
} // namespace