| // 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. |
| |
| #ifndef CRASH_REPORTER_CRASH_COLLECTOR_H_ |
| #define CRASH_REPORTER_CRASH_COLLECTOR_H_ |
| |
| #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 { |
| public: |
| typedef bool (*IsFeedbackAllowedFunction)(); |
| |
| enum CrashDirectorySelectionMethod { |
| // Force reports to be stored in the user crash directory, even if we are |
| // not running as the "chronos" user. |
| kAlwaysUseUserCrashDirectory, |
| // 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. |
| kUseNormalCrashDirectorySelectionMethod |
| }; |
| |
| 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. |
| kNormalCrashSendMode, |
| // 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. |
| kCrashLoopSendingMode |
| }; |
| |
| enum ErrorType { |
| kErrorNone, |
| kErrorSystemIssue, |
| kErrorReadCoreData, |
| kErrorUnusableProcFiles, |
| kErrorInvalidCoreFile, |
| kErrorUnsupported32BitCoreFile, |
| kErrorCore2MinidumpConversion, |
| }; |
| |
| 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 = ""); |
| |
| 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(IsFeedbackAllowedFunction is_metrics_allowed, 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); |
| |
| protected: |
| friend class CrashCollectorTest; |
| FRIEND_TEST(ArcContextTest, GetAndroidVersion); |
| FRIEND_TEST(ChromeCollectorTest, HandleCrash); |
| FRIEND_TEST(CrashCollectorTest, CrashLoopModeCreatesInMemoryCompressedFiles); |
| FRIEND_TEST(CrashCollectorTest, |
| DISABLED_CrashLoopModeCreatesInMemoryCompressedFiles); |
| FRIEND_TEST(CrashCollectorTest, CrashLoopModeCreatesInMemoryFiles); |
| FRIEND_TEST(CrashCollectorTest, DISABLED_CrashLoopModeCreatesInMemoryFiles); |
| FRIEND_TEST(CrashCollectorTest, CrashLoopModeCreatesMultipleInMemoryFiles); |
| FRIEND_TEST(CrashCollectorTest, |
| DISABLED_CrashLoopModeCreatesMultipleInMemoryFiles); |
| FRIEND_TEST(CrashCollectorTest, |
| CrashLoopModeWillNotCreateDuplicateCompressedFileNames); |
| FRIEND_TEST(CrashCollectorTest, |
| DISABLED_CrashLoopModeWillNotCreateDuplicateCompressedFileNames); |
| FRIEND_TEST(CrashCollectorTest, CrashLoopModeWillNotCreateDuplicateFileNames); |
| FRIEND_TEST(CrashCollectorTest, |
| DISABLED_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, |
| CreateDirectoryWithSettings_FixPermissionsShallow); |
| FRIEND_TEST(CrashCollectorTest, |
| CreateDirectoryWithSettings_FixPermissionsRecursive); |
| FRIEND_TEST(CrashCollectorTest, |
| RunAsRoot_CreateDirectoryWithSettings_FixOwners); |
| FRIEND_TEST(CrashCollectorTest, |
| CreateDirectoryWithSettings_FixSubdirPermissions); |
| 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, |
| RemoveNewFileFailsOnNonExistantFilesInCrashLoopMode); |
| FRIEND_TEST(CrashCollectorTest, RemoveNewFileRemovesCompressedFiles); |
| FRIEND_TEST(CrashCollectorTest, |
| RemoveNewFileRemovesCompressedFilesInCrashLoopMode); |
| FRIEND_TEST(CrashCollectorTest, |
| DISABLED_RemoveNewFileRemovesCompressedFilesInCrashLoopMode); |
| FRIEND_TEST(CrashCollectorTest, |
| RemoveNewFileRemovesCorrectFileInCrashLoopMode); |
| FRIEND_TEST(CrashCollectorTest, |
| DISABLED_RemoveNewFileRemovesCorrectFileInCrashLoopMode); |
| FRIEND_TEST(CrashCollectorTest, RemoveNewFileRemovesNormalFiles); |
| FRIEND_TEST(CrashCollectorTest, |
| RemoveNewFileRemovesNormalFilesInCrashLoopMode); |
| FRIEND_TEST(CrashCollectorTest, |
| DISABLED_RemoveNewFileRemovesNormalFilesInCrashLoopMode); |
| 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. |
| virtual std::string GetOsVersion() const; |
| |
| // Returns the OS milestone written to the metadata file. |
| virtual std::string GetOsMilestone() const; |
| |
| // Returns the OS description written to the metadata file. |
| virtual std::string GetOsDescription() 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(); |
| |
| IsFeedbackAllowedFunction is_feedback_allowed_function_; |
| 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. |
| std::unique_ptr<org::chromium::SessionManagerInterfaceProxyInterface> |
| session_manager_proxy_; |
| |
| // 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_; |
| |
| // Hash a string to a number. We define our own hash function to not |
| // be dependent on a C++ library that might change. |
| static unsigned HashString(base::StringPiece input); |
| |
| // 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; |
| |
| private: |
| 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>> |
| in_memory_files_; |
| |
| // 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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CrashCollector); |
| }; |
| |
| #endif // CRASH_REPORTER_CRASH_COLLECTOR_H_ |