blob: 2a4cc7fa201b2c3bd1080b7e63110fcd71a4e0ba [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.
// The crash collector is a base class for all collectors to use. It implements
// common functionality, such as writing out .meta files.
// It is not a collector in and of itself.
#include <sys/stat.h>
#include <sys/types.h>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/macros.h>
#include <base/optional.h>
#include <base/time/clock.h>
#include <base/time/time.h>
#include <brillo/dbus/file_descriptor.h>
#include <gtest/gtest_prod.h> // for FRIEND_TEST
#include <session_manager/dbus-proxies.h>
#include <debugd/dbus-proxies.h>
#include <metrics/metrics_library.h>
constexpr mode_t kSystemCrashFilesMode = 0660;
// User crash collector.
class CrashCollector {
enum CrashDirectorySelectionMethod {
// Force reports to be stored in the user crash directory, even if we are
// not running as the "chronos" user.
// Use the normal crash directory selection process: Store in the user crash
// directory if running as the "chronos" user, otherwise store in the system
// crash directory.
enum CrashSendingMode {
// Use the normal crash sending mode: Write crash files out to disk, and
// assume crash_sender will be along later to send them out.
// Use a special mode suitable when we are in a login-crash-loop. where
// Chrome keeps crashing right after login, and we're about to log the user
// out because we can't get into a good logged-in state. Write the crash
// files into special in-memory locations, since the normal user crash
// directory is in the cryptohome which will be locked out momentarily, and
// send those in-memory files over to debugd for immediate upload, since
// they are in volatile storage and the user may turn off their machine in
// frustration shortly.
enum ErrorType {
explicit CrashCollector(const std::string& collector_name,
const std::string& tag = "");
explicit CrashCollector(
const std::string& collector_name,
CrashDirectorySelectionMethod crash_directory_selection_method,
CrashSendingMode crash_sending_mode,
const std::string& tag = "");
CrashCollector(const CrashCollector&) = delete;
CrashCollector& operator=(const CrashCollector&) = delete;
virtual ~CrashCollector();
void set_lsb_release_for_test(const base::FilePath& lsb_release) {
lsb_release_ = lsb_release;
// For testing, set the directory always returned by
// GetCreatedCrashDirectoryByEuid.
void set_crash_directory_for_test(const base::FilePath& forced_directory) {
forced_crash_directory_ = forced_directory;
// For testing, set the directory where cached files are stored instead of
// kCrashReporterStatePath.
void set_reporter_state_directory_for_test(
const base::FilePath& forced_directory) {
crash_reporter_state_path_ = forced_directory;
void set_metrics_library_for_test(
std::unique_ptr<MetricsLibraryInterface> metrics_lib) {
metrics_lib_ = std::move(metrics_lib);
// For testing, set the log config file path instead of kDefaultLogConfig.
void set_log_config_path(const std::string& path) {
log_config_path_ = base::FilePath(path);
// For testing, set the clock to use to get the report timestamp.
void set_test_clock(std::unique_ptr<base::Clock> test_clock) {
test_clock_ = std::move(test_clock);
// For testing, use to set the kernel version rather than relying on uname.
void set_test_kernel_info(const std::string& kernel_name,
const std::string& kernel_version) {
test_kernel_name_ = kernel_name;
test_kernel_version_ = kernel_version;
// For testing, return the in-memory files generated when in
// kCrashLoopSendingMode. Since in_memory_files_ is a move-only type, this
// clears the in_memory_files_ member variable.
std::vector<std::tuple<std::string, brillo::dbus_utils::FileDescriptor>>
get_in_memory_files_for_test() {
return std::move(in_memory_files_);
// Initialize the crash collector for detection of crashes, given a
// metrics collection enabled oracle.
void Initialize(bool early);
// Return the number of bytes successfully written by all calls to
// WriteNewFile() and WriteNewCompressedFile() so far. For
// WriteNewCompressedFile(), the count is of bytes on disk, after compression.
off_t get_bytes_written() const { return bytes_written_; }
// Initialize the system crash paths.
static bool InitializeSystemCrashDirectories(bool early);
// Initialize metrics path. Returns true if flag directory is created.
static bool InitializeSystemMetricsDirectories();
// Add non-standard meta data to the crash metadata file. Call
// before calling FinishCrash. Key must not contain "=" or "\n" characters.
// Value must not contain "\n" characters.
void AddCrashMetaData(const std::string& key, const std::string& value);
friend class CrashCollectorTest;
FRIEND_TEST(ArcContextTest, GetAndroidVersion);
FRIEND_TEST(ChromeCollectorTest, HandleCrash);
FRIEND_TEST(CrashCollectorTest, CrashLoopModeCreatesInMemoryCompressedFiles);
FRIEND_TEST(CrashCollectorTest, CrashLoopModeCreatesInMemoryFiles);
FRIEND_TEST(CrashCollectorTest, DISABLED_CrashLoopModeCreatesInMemoryFiles);
FRIEND_TEST(CrashCollectorTest, CrashLoopModeCreatesMultipleInMemoryFiles);
FRIEND_TEST(CrashCollectorTest, CrashLoopModeWillNotCreateDuplicateFileNames);
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityCorrectBasename);
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityStrangeNames);
FRIEND_TEST(CrashCollectorTest, CheckHasCapacityUsual);
FRIEND_TEST(CrashCollectorTest, CreateDirectoryWithSettingsMode);
FRIEND_TEST(CrashCollectorTest, CreateDirectoryWithSettingsNonDir);
FRIEND_TEST(CrashCollectorTest, CreateDirectoryWithSettingsSubdir);
FRIEND_TEST(CrashCollectorTest, CreateDirectoryWithSettingsSymlinks);
FRIEND_TEST(CrashCollectorTest, FormatDumpBasename);
FRIEND_TEST(CrashCollectorTest, GetCrashDirectoryInfo);
FRIEND_TEST(CrashCollectorTest, GetCrashDirectoryInfoLoggedOut);
FRIEND_TEST(CrashCollectorTest, GetCrashPath);
FRIEND_TEST(CrashCollectorTest, GetLogContents);
FRIEND_TEST(CrashCollectorTest, GetMultipleLogContents);
FRIEND_TEST(CrashCollectorTest, GetProcessTree);
FRIEND_TEST(CrashCollectorTest, GetUptime);
FRIEND_TEST(CrashCollectorTest, Initialize);
FRIEND_TEST(CrashCollectorParameterizedTest, MetaData);
FRIEND_TEST(CrashCollectorTest, ErrorCollectionMetaData);
FRIEND_TEST(CrashCollectorTest, MetaDataDoesntCreateSymlink);
FRIEND_TEST(CrashCollectorTest, MetaDataDoesntOverwriteSymlink);
FRIEND_TEST(CrashCollectorTest, CollectionLogsToUMA);
FRIEND_TEST(CrashCollectorTest, ParseProcessTicksFromStat);
FRIEND_TEST(CrashCollectorTest, Sanitize);
FRIEND_TEST(CrashCollectorTest, StripMacAddressesBasic);
FRIEND_TEST(CrashCollectorTest, StripMacAddressesBulk);
FRIEND_TEST(CrashCollectorTest, StripSensitiveDataSample);
FRIEND_TEST(CrashCollectorTest, StripEmailAddresses);
FRIEND_TEST(CrashCollectorTest, StripSerialNumbers);
FRIEND_TEST(CrashCollectorTest, RemoveNewFileFailsOnNonExistantFiles);
FRIEND_TEST(CrashCollectorTest, RemoveNewFileRemovesCompressedFiles);
FRIEND_TEST(CrashCollectorTest, RemoveNewFileRemovesNormalFiles);
FRIEND_TEST(CrashCollectorTest, TruncatedLog);
FRIEND_TEST(CrashCollectorTest, WriteNewFile);
FRIEND_TEST(CrashCollectorTest, WriteNewCompressedFile);
FRIEND_TEST(CrashCollectorTest, WriteNewCompressedFileFailsIfFileExists);
// Default value if OS version/description cannot be determined.
static const char* const kUnknownValue;
// Set maximum enqueued crashes in a crash directory.
static const int kMaxCrashDirectorySize;
// UID for root account.
static const uid_t kRootUid;
// Set up D-Bus.
virtual void SetUpDBus();
// Creates a new file and returns a file descriptor to it.
base::ScopedFD GetNewFileHandle(const base::FilePath& filename);
// Writes |data| of |size| to |filename|, which must be a new file.
// If the file already exists or writing fails, return a negative value.
// Otherwise returns the number of bytes written.
int WriteNewFile(const base::FilePath& filename, const char* data, int size);
// Writes |data| of |size| to |filename|, which must be a new file ending in
// ".gz". File will be a gzip-compressed file. Returns true on success,
// false on failure.
bool WriteNewCompressedFile(const base::FilePath& filename,
const char* data,
size_t size);
// Deletes a file created by WriteNewFile() or WriteNewCompressedFile(). Also
// decrements get_bytes_written() by the file size. Needed because
// base::DeleteFile() doesn't work on files created when in
// kCrashLoopSendingMode.
bool RemoveNewFile(const base::FilePath& filename);
// Return a filename that has only [a-z0-1_] characters by mapping
// all others into '_'.
std::string Sanitize(const std::string& name);
// Strip any data that the user might not want sent up to the crash server.
// |contents| is modified in-place.
void StripSensitiveData(std::string* contents);
void StripMacAddresses(std::string* contents);
void StripEmailAddresses(std::string* contents);
void StripSerialNumbers(std::string* contents);
bool GetUserCrashDirectories(std::vector<base::FilePath>* directories,
bool use_non_chronos_cryptohome);
base::FilePath GetUserCrashDirectory(bool use_non_chronos_cryptohome);
base::Optional<base::FilePath> GetCrashDirectoryInfo(
uid_t process_euid,
uid_t default_user_id,
bool use_non_chronos_cryptohome,
mode_t* mode,
uid_t* directory_owner,
gid_t* directory_group);
// Determines the crash directory for given euid, and creates the directory if
// necessary with appropriate permissions. If |out_of_capacity| is not
// nullptr, it is set to indicate if the call failed due to not having
// capacity in the crash directory. Returns true whether or not directory
// needed to be created, false on any failure. If the crash directory is at
// capacity, returns false. If |use_non_chronos_cryptohome| is set, use the
// new crash directory under /run/daemon-store/crash/<user-hash>.
bool GetCreatedCrashDirectoryByEuid(uid_t euid,
base::FilePath* crash_file_path,
bool* out_of_capacity,
bool use_non_chronos_cryptohome = false);
// Create a directory using the specified mode/user/group, and make sure it
// is actually a directory with the specified permissions.
// If |files_mode| is set, the call will recursively change permissions on
// |dir| such that:
// * any directories under it in the file heirarchy have mode |mode|
// * any files under it in the heirarchy have mode |files_mode|
// * all files AND directories under it are owned by owner:group
static bool CreateDirectoryWithSettings(const base::FilePath& dir,
mode_t mode,
uid_t owner,
gid_t group,
int* dir_fd,
mode_t files_mode = 0);
// Format crash name based on components.
std::string FormatDumpBasename(const std::string& exec_name,
time_t timestamp,
pid_t pid);
// Create a file path to a file in |crash_directory| with the given
// |basename| and |extension|.
base::FilePath GetCrashPath(const base::FilePath& crash_directory,
const std::string& basename,
const std::string& extension);
// Returns the path /proc/<pid>.
static base::FilePath GetProcessPath(pid_t pid);
static bool GetUptime(base::TimeDelta* uptime);
static bool GetUptimeAtProcessStart(pid_t pid, base::TimeDelta* uptime);
virtual bool GetExecutableBaseNameFromPid(pid_t pid, std::string* base_name);
// Check given crash directory still has remaining capacity for another
// crash.
bool CheckHasCapacity(const base::FilePath& crash_directory);
bool CheckHasCapacity(const base::FilePath& crash_directory,
const std::string& display_path);
// Write a log applicable to |exec_name| to |output_file| based on the
// log configuration file at |config_path|. If |output_file| ends in .gz, it
// will be compressed in gzip format, otherwise it will be plaintext.
bool GetLogContents(const base::FilePath& config_path,
const std::string& exec_name,
const base::FilePath& output_file);
// Write logs applicable to |exec_names| to |output_file| based on the
// log configuration file at |config_path|. If |output_file| ends in .gz, it
// will be compressed in gzip format, otherwise it will be plaintext.
// This function returns false only if all of the log commands fail to run.
bool GetMultipleLogContents(const base::FilePath& config_path,
const std::vector<std::string>& exec_names,
const base::FilePath& output_file);
// Write details about the process tree of |pid| to |output_file|.
bool GetProcessTree(pid_t pid, const base::FilePath& output_file);
// Add a file to be uploaded to the crash reporter server. The file must
// persist until the crash report is sent; ideally it should live in the same
// place as the .meta file, so it can be cleaned up automatically.
void AddCrashMetaUploadFile(const std::string& key, const std::string& path);
// Add non-standard meta data to the crash metadata file.
// Data added though this call will be uploaded to the crash reporter server,
// appearing as a form field. Virtual for testing.
virtual void AddCrashMetaUploadData(const std::string& key,
const std::string& value);
// Like AddCrashMetaUploadData, but loads the value from the file at |path|.
// The file is not uploaded as an attachment, unlike AddCrashMetaUploadFile.
void AddCrashMetaUploadText(const std::string& key, const std::string& path);
// Gets the corresponding value for |key| from the lsb-release file.
std::string GetLsbReleaseValue(const std::string& key) const;
// Returns the OS version written to the metadata file.
std::string GetOsVersion() const;
// Returns the OS milestone written to the metadata file.
std::string GetOsMilestone() const;
// Returns the OS description written to the metadata file.
std::string GetOsDescription() const;
// Returns the channel name written to the metadata file.
std::string GetChannel() const;
// Returns the product version written to the metadata file.
virtual std::string GetProductVersion() const;
// Returns the kernel name from uname (e.g. "Linux").
std::string GetKernelName() const;
// Returns the uname string formatted as
// 3.8.11 #1 SMP Wed Aug 22 02:18:30 PDT 2018
std::string GetKernelVersion() const;
// Called after all files have been written and we want to send out this
// crash. Write a file of metadata about crash and, if in crash-loop mode,
// sends the UploadSingleCrash message to debugd. Not called if we failed to
// make a good crash report.
virtual void FinishCrash(const base::FilePath& meta_path,
const std::string& exec_name,
const std::string& payload_name);
// Returns true if chrome crashes should be handled.
bool ShouldHandleChromeCrashes();
std::string extra_metadata_;
const std::string collector_name_;
base::FilePath forced_crash_directory_;
base::FilePath lsb_release_;
base::FilePath system_crash_path_;
base::FilePath crash_reporter_state_path_;
base::FilePath log_config_path_;
size_t max_log_size_;
std::unique_ptr<base::Clock> test_clock_;
std::string test_kernel_name_;
std::string test_kernel_version_;
scoped_refptr<dbus::Bus> bus_;
// D-Bus proxy for session manager interface.
// D-Bus proxy for debugd interface.
std::unique_ptr<org::chromium::debugdProxyInterface> debugd_proxy_;
// If kCrashLoopSendingMode, reports are stored in memory and sent over DBus
// to debugd when finished. Otherwise, we store the crash reports on disk and
// rely on crash_sender to later pick it up and send it.
const CrashSendingMode crash_sending_mode_;
// Record information about a crash collector failure in a new crash report.
// Clears metadata for existing report.
// orig_exec: Name of the executable in which we were processing a crash when
// the failure happened.
void EnqueueCollectionErrorLog(ErrorType error_type,
const std::string& orig_exec);
// Logs a |message| detailing a crash, along with the |reason| for which the
// collector handled or ignored it.
void LogCrash(const std::string& message, const std::string& reason) const;
static bool ParseProcessTicksFromStat(base::StringPiece stat,
uint64_t* ticks);
// Should reports always be stored in the user crash directory, or can they be
// stored in the system directory if we are not running as "chronos"?
const CrashDirectorySelectionMethod crash_directory_selection_method_;
// True when FinishCrash has been called. Once true, no new files should be
// created.
bool is_finished_;
// If crash_loop_mode_ is true, all files are collected in here instead of
// being written to disk. The first element of the tuple is the base filename,
// the second is a memfd_create file descriptor with the file contents.
std::vector<std::tuple<std::string, brillo::dbus_utils::FileDescriptor>>
// Number of bytes successfully written by all calls to WriteNewFile() and
// WriteNewCompressedFile() so far. For WriteNewCompressedFile(), the count is
// of bytes on disk, after compression.
off_t bytes_written_;
// Returns true if there is already a file in in_memory_files_ with
// filename.BaseName().
bool InMemoryFileExists(const base::FilePath& filename) const;
// Returns an error type signature for a given |error_type| value,
// which is reported to the crash server along with the
// crash_reporter-user-collection signature.
std::string GetErrorTypeSignature(ErrorType error_type) const;
// Prepended to log messages to differentiate between collectors.
const std::string tag_;
std::unique_ptr<MetricsLibraryInterface> metrics_lib_;