| // 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 <algorithm> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/stl_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/process.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"; |
| |
| // Metadata fields for crash server. |
| const char kProductNameField[] = "prod"; |
| const char kProcessTypeField[] = "ptype"; |
| |
| // Product name of Chrome for Chrome OS on the crash server. See |
| // ChromeCrashReporterClient::GetProductNameAndVersion(). |
| const char kChromeProductName[] = "Chrome_ChromeOS"; |
| |
| // Process type for chrome --mash crashes. |
| const char kMashProcessType[] = "mash"; |
| |
| // 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); |
| |
| } // namespace |
| |
| UserCollector::UserCollector() |
| : UserCollectorBase("user", false), |
| core_pattern_file_(kCorePatternFile), |
| core_pipe_limit_file_(kCorePipeLimitFile), |
| filter_path_(kFilterPath), |
| core2md_failure_(false) { |
| } |
| |
| void UserCollector::Initialize( |
| UserCollector::CountCrashFunction count_crash_function, |
| const std::string &our_path, |
| UserCollector::IsFeedbackAllowedFunction is_feedback_allowed_function, |
| bool generate_diagnostics, |
| bool core2md_failure, |
| bool directory_failure, |
| const std::string &filter_in, |
| FilterOutFunction filter_out) { |
| UserCollectorBase::Initialize(count_crash_function, |
| is_feedback_allowed_function, |
| generate_diagnostics, |
| directory_failure, |
| filter_in); |
| our_path_ = our_path; |
| core2md_failure_ = core2md_failure; |
| filter_out_ = std::move(filter_out); |
| } |
| |
| UserCollector::~UserCollector() { |
| } |
| |
| // 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, and the logging_UserCrash.py autotest. |
| std::string UserCollector::GetPattern(bool enabled) const { |
| if (enabled) { |
| // Combine the four 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 --user=%%P:%%s:%%u:%%e", our_path_.c_str()); |
| } else { |
| return "core"; |
| } |
| } |
| |
| bool UserCollector::SetUpInternal(bool enabled) { |
| 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); |
| if (base::WriteFile(FilePath(core_pattern_file_), pattern.c_str(), |
| pattern.length()) != static_cast<int>(pattern.length())) { |
| PLOG(ERROR) << "Unable to write " << core_pattern_file_; |
| return false; |
| } |
| 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; |
| } |
| static const char* const kProcFiles[] = { |
| "auxv", |
| "cmdline", |
| "environ", |
| "maps", |
| "status" |
| }; |
| for (std::string proc_file : kProcFiles) { |
| if (!base::CopyFile(process_path.Append(proc_file), |
| container_dir.Append(proc_file))) { |
| LOG(ERROR) << "Could not copy " << proc_file << " 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)) { |
| LOG(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; |
| } |
| |
| bool UserCollector::CopyStdinToCoreFile(const FilePath &core_path) { |
| // Copy off all stdin to a core file. |
| FilePath stdin_path("/dev/fd/0"); |
| if (base::CopyFile(stdin_path, 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, false); |
| 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; |
| } |
| |
| if (!base::PathExists(minidump_path)) { |
| LOG(ERROR) << "Minidump file " << minidump_path.value() |
| << " was not created"; |
| 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::IsChromeMashProcess(int pid) const { |
| std::vector<std::string> args = GetCommandLine(pid); |
| // Flags must be kept in sync with chrome's switches::kMash and kMus, see |
| // src/chrome/common/chrome_switches.cc. |
| static const char kMashFlag[] = "--mash"; // "ash" process |
| static const char kMusFlag[] = "--mus"; // "mus-ws" and "mus-gpu" processes |
| return ContainsValue(args, kMashFlag) || ContainsValue(args, kMusFlag); |
| } |
| |
| bool UserCollector::ShouldDump(pid_t pid, |
| bool has_owner_consent, |
| bool is_developer, |
| bool handle_chrome_crashes, |
| const std::string &exec, |
| std::string *reason) { |
| reason->clear(); |
| |
| if (filter_out_(pid)) { |
| *reason = "ignoring - PID filtered out"; |
| return false; |
| } |
| |
| // 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)) { |
| if (!IsChromeMashProcess(pid)) { |
| *reason = "ignoring call by kernel - chrome crash; " |
| "waiting for chrome to call us directly"; |
| return false; |
| } |
| // For mustash, chrome --mash and its non-browser mojo services are |
| // considered system services and are handled as user crashes. |
| LOG(INFO) << "chrome mash process crash"; |
| } |
| |
| if (!RunFilter(pid)) { |
| *reason = "filtered out"; |
| return false; |
| } |
| |
| return UserCollectorBase::ShouldDump(has_owner_consent, is_developer, reason); |
| } |
| |
| bool UserCollector::ShouldDump(pid_t pid, |
| uid_t, |
| const std::string &exec, |
| std::string *reason) { |
| return ShouldDump(pid, |
| is_feedback_allowed_function_(), |
| IsDeveloperImage(), |
| 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; |
| } |
| |
| void UserCollector::AddExtraMetadata(const std::string &exec, pid_t pid) { |
| if (!IsChromeExecName(exec) || !IsChromeMashProcess(pid)) |
| return; |
| |
| // For mustash, chrome --mash crash reports are handled as user crashes but |
| // are tagged as the chrome product on crash server so the crash dashboard |
| // can show chrome tooling and they show up in chrome crash triage. |
| AddCrashMetaUploadData(kProductNameField, kChromeProductName); |
| AddCrashMetaUploadData(kProcessTypeField, kMashProcessType); |
| } |
| |
| 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 ContainsKey(chrome_names, exec); |
| } |
| |
| } // namespace |