/* * Copyright (C) 2015 The Android Open Source Project * * 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. */ #define LOG_TAG "crash_collector" #include <dirent.h> #include <errno.h> #include <pwd.h> #include <sys/capability.h> #include <sys/types.h> #include <unistd.h> #include <string> #include <vector> #include <android-base/file.h> #include <cutils/properties.h> #include <log/log.h> #include <private/android_filesystem_config.h> #include <utils/String8.h> #include "client/linux/minidump_writer/linux_core_dumper.h" #include "client/linux/minidump_writer/minidump_writer.h" #undef DISALLOW_COPY_AND_ASSIGN // Defined in breakpad's header. #include "coredump_writer.h" namespace { using android::String8; const char kOutputDirectory[] = "/data/system/crash_reports"; const int kMaxNumReports = 16; // Gets a list of entries under the specified directory. bool ReadDirectory(const std::string& path, std::vector<dirent>* entries) { std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(path.c_str()), closedir); if (!dir) return false; while (struct dirent* entry = readdir(dir.get())) { if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) entries->push_back(*entry); } return true; } // Removes a file or a directory recursively. bool RemoveRecursively(const std::string& path) { if (unlink(path.c_str()) == 0) return true; if (errno != EISDIR) { ALOGE("Failed to unlink: %s, errno = %d", path.c_str(), errno); return false; } std::vector<dirent> entries; if (!ReadDirectory(path, &entries)) return false; for (const auto& entry : entries) { if (!RemoveRecursively(path + "/" + entry.d_name)) return false; } return rmdir(path.c_str()) == 0; } // Makes room for the new crash report by deleting old files when necessary. bool MakeRoomForNewReport() { // Enumerate reports. std::vector<dirent> entries; if (!ReadDirectory(kOutputDirectory, &entries)) return false; std::vector<time_t> dump_mtimes; // Modification time of dump files. std::vector<std::pair<time_t, String8>> all_files; for (const auto& entry : entries) { String8 filename = String8(kOutputDirectory).appendPath(entry.d_name); struct stat attributes; if (stat(filename.string(), &attributes)) return false; all_files.push_back(std::make_pair(attributes.st_mtime, filename)); if (filename.getPathExtension() == ".dmp") dump_mtimes.push_back(attributes.st_mtime); } // Remove old files. if (dump_mtimes.size() >= kMaxNumReports) { // Sort the vector (newer file comes first). std::sort(dump_mtimes.rbegin(), dump_mtimes.rend()); const time_t threshold = dump_mtimes[kMaxNumReports - 1]; for (const auto& file : all_files) { const time_t mtime = file.first; const String8& filename = file.second; if (mtime <= threshold) { if (!RemoveRecursively(filename.string())) return false; } } } return true; } // Returns the specified system property. std::string GetSystemProperty(const std::string& key) { char buf[PROPERTY_VALUE_MAX]; property_get(key.c_str(), buf, ""); return std::string(buf); } // Writes metadata as JSON file. bool WriteMetadata(ssize_t result_coredump_size, size_t coredump_size_limit, size_t expected_coredump_size, const std::string& pid, const std::string& uid, const std::string& gid, const std::string& signal, const std::string& username, const std::string& exec_name, const std::string& filename) { std::string content = "{"; content += "\"version\":\"" + GetSystemProperty("ro.build.id") + "\""; content += ","; content += "\"result_coredump_size\":" + std::to_string(result_coredump_size); content += ","; content += "\"coredump_size_limit\":" + std::to_string(coredump_size_limit); content += ","; content += "\"expected_coredump_size\":" + std::to_string(expected_coredump_size); content += ","; content += "\"pid\":" + pid; content += ","; content += "\"uid\":" + uid; content += ","; content += "\"gid\":" + gid; content += ","; content += "\"signal\":" + signal; content += ","; content += "\"username\":\"" + username + "\""; content += ","; content += "\"process\":\"" + exec_name + "\""; content += "}"; return android::base::WriteStringToFile( content, filename, S_IRUSR | S_IWUSR, AID_SYSTEM, AID_SYSTEM); } // Converts the specified coredump file to a minidump. bool ConvertCoredumpToMinidump(const std::string& coredump_filename, const std::string& proc_files_dir, const std::string& minidump_filename) { google_breakpad::MappingList mappings; google_breakpad::AppMemoryList memory_list; google_breakpad::LinuxCoreDumper dumper( 0, coredump_filename.c_str(), proc_files_dir.c_str()); bool success = google_breakpad::WriteMinidump( minidump_filename.c_str(), mappings, memory_list, &dumper); unlink(coredump_filename.c_str()); RemoveRecursively(proc_files_dir); return success; } } // namespace int main(int argc, char** argv) { if (argc < 7) { ALOGE("Insufficient args."); return 1; } const std::string pid_string = argv[1]; const std::string uid_string = argv[2]; const std::string gid_string = argv[3]; const std::string signal_string = argv[4]; const std::string crash_time = argv[5]; const std::string exec_name = argv[6]; const uid_t uid = std::stoi(uid_string); const uid_t appid = uid % AID_USER; if (appid >= AID_APP) { // Ignore non-system crashes. return 0; } // Act as the system user and drop all capabilities. struct __user_cap_header_struct capheader; struct __user_cap_data_struct capdata[2]; memset(&capheader, 0, sizeof(capheader)); memset(&capdata, 0, sizeof(capdata)); capheader.version = _LINUX_CAPABILITY_VERSION_3; if (setegid(AID_SYSTEM) != 0 || seteuid(AID_SYSTEM) != 0 || capset(&capheader, capdata) != 0) { ALOGE("Failed to stop being root."); return 1; } // Username lookup. passwd* pwd = getpwuid(appid); std::string username((pwd != NULL) ? pwd->pw_name : ""); // Delete old crash reports. if (!MakeRoomForNewReport()) { ALOGE("Failed to delete old crash reports."); return 1; } // Read coredump from stdin. const std::string basename = std::string(kOutputDirectory) + "/" + crash_time + "." + pid_string; const std::string coredump = basename + ".core"; const std::string proc_files_dir = basename + ".proc"; if (mkdir(proc_files_dir.c_str(), S_IRUSR | S_IWUSR | S_IXUSR) == -1) { ALOGE("Failed to create proc directory. errno = %d", errno); return 1; } CoredumpWriter coredump_writer(STDIN_FILENO, coredump, proc_files_dir); const ssize_t result_coredump_size = coredump_writer.WriteCoredump(); if (result_coredump_size > 0) { // Convert coredump to minidump. const std::string minidump = basename + ".dmp"; if (!ConvertCoredumpToMinidump(coredump, proc_files_dir, minidump)) { ALOGE("Failed to convert coredump to minidump."); } } else { ALOGE("Failed to copy coredump from stdin."); } // Write metadata. const std::string metadata = basename + ".meta"; if (!WriteMetadata(result_coredump_size, coredump_writer.coredump_size_limit(), coredump_writer.expected_coredump_size(), pid_string, uid_string, gid_string, signal_string, username, exec_name, metadata)) { ALOGE("Failed to write metadata."); return 1; } return 0; }