/* * Copyright 2021 Soo Hwan Na "Royna" * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "LoggerInternal.h" using android::base::GetBoolProperty; using android::base::GetProperty; using android::base::WaitForProperty; using android::base::WriteStringToFile; using std::chrono_literals::operator""s; // NOLINT (misc-unused-using-decls) namespace fs = std::filesystem; #define MAKE_LOGGER_PROP(prop) "persist.ext.logdump." prop struct Logcat { constexpr static std::string_view NAME = "logcat"; constexpr static std::string_view LOGC = "logcat"; struct Data { FILE *out_buf; FILE *err_buf; pid_t pid; }; using HANDLE = std::unique_ptr; static HANDLE open() { HANDLE empty{nullptr, +[](Data * /*file*/) {}}; std::array out_fds = {}; std::array err_fds = {}; if (pipe(out_fds.data()) || pipe(err_fds.data())) { PLOG(ERROR) << "Failed to create pipe"; return empty; } pid_t pid = vfork(); if (pid < 0) { PLOG(ERROR) << "Failed to fork"; return empty; } else if (pid == 0) { dup2(out_fds[1], STDOUT_FILENO); dup2(err_fds[1], STDERR_FILENO); ::close(out_fds[0]); ::close(err_fds[0]); execlp(LOGC.data(), LOGC.data(), nullptr); _exit(std::numeric_limits::max()); } else { ::close(out_fds[1]); ::close(err_fds[1]); int status = 0; if (waitpid(pid, &status, WNOHANG) != 0) { LOG(ERROR) << "Proc early-exited with error code " << WEXITSTATUS(status); return empty; } LOG(INFO) << "Forked exe " << std::quoted(LOGC) << " with pid: " << pid; return {new Data{fdopen(out_fds[0], "r"), fdopen(err_fds[0], "r"), pid}, &close}; } } static void close(Data *data) { ::kill(data->pid, SIGTERM); ::waitpid(data->pid, nullptr, 0); if (data->out_buf != nullptr) { ::fflush(data->out_buf); ::fclose(data->out_buf); } if (data->err_buf != nullptr) { std::array errbuf{}; if (::fread(errbuf.data(), errbuf.size(), 1, data->err_buf) != 0) { LOG(ERROR) << "standard error output" << errbuf.data(); } ::fflush(data->err_buf); ::fclose(data->err_buf); } delete data; } template static const char *fgets(std::array &data, const HANDLE &handle) { return ::fgets(data.data(), data.size(), handle->out_buf); } }; struct Dmesg { constexpr static std::string_view NAME = "dmesg"; constexpr static std::string_view FILEC = "/proc/kmsg"; using HANDLE = std::unique_ptr; static HANDLE open() { return {fopen(FILEC.data(), "r"), &fclose}; } template static const char *fgets(std::array &data, const HANDLE &handle) { return ::fgets(data.data(), data.size(), handle.get()); } }; struct Filter { static bool write(const std::filesystem::path &file, const std::set &results) { if (results.empty()) { return true; } std::ofstream fileStream(file); if (!fileStream.is_open()) { PLOG(ERROR) << "Failed to open file: " << file; return false; } fileStream << fmt::format("{}", fmt::join(results, "\n")); fileStream.close(); return true; } }; struct FilterAvc : Filter { constexpr static std::string_view NAME = "avc"; static bool filter(const std::string &line) { // Matches "avc: denied { ioctl } for comm=..." for example const static auto kAvcMessageRegEX = std::regex( R"(avc:\s+denied\s+\{(\s\w+)+\s\}\sfor\s)", std::regex::ECMAScript); bool match = std::regex_search(line, kAvcMessageRegEX, std::regex_constants::match_not_null); return match && line.find("untrusted_app") == std::string::npos; } }; struct FilterAvcGen : Filter { constexpr static std::string_view NAME = "generated_sepolicy"; static bool filter(const std::string &line) { AvcContext ctx(line); return !ctx.stale; } static bool write(const std::filesystem::path &file, const std::set &results) { if (results.empty()) { return true; } // Translate to AVCContexts vector AvcContexts contexts; std::transform(results.begin(), results.end(), std::back_inserter(contexts), [](const std::string &file) { return AvcContext(file); }); // Combine AVC contexts for (auto &e1 : contexts) { for (auto &e2 : contexts) { if (&e1 == &e2) { continue; } e1 += e2; } } // Write to file std::ofstream fileStream(file); if (!fileStream.is_open()) { PLOG(ERROR) << "Failed to open file: " << file; return false; } fileStream << fmt::format("{}\n", contexts); fileStream.close(); return true; } }; /** * Start the associated logger * * @param run Pointer to run/stop control variable */ template void start(const std::filesystem::path &directory, std::atomic_bool *run) { std::array buf = {0}; // Open log source typename Logger::HANDLE _fp = Logger::open(); if (_fp == nullptr) { LOG(ERROR) << "Failed to open source for logger " << Logger::NAME; return; } // Open log destination std::filesystem::path logPath(directory / fmt::format("{}.txt", Logger::NAME)); std::ofstream logFile(logPath); if (!logFile.is_open()) { PLOG(ERROR) << "Failed to open " << logPath << " for logging"; return; } std::tuple>...> filters{}; while (*run) { const char *ret = Logger::fgets(buf, _fp); std::istringstream ss(buf.data()); std::string line; if (ret == nullptr) { continue; } while (std::getline(ss, line)) { std::apply( [&line](auto &...filter) { ( [&] { if (filter.first.filter(line)) { filter.second.insert(line); } }(), ...); }, filters); logFile << line << '\n'; } } _fp.reset(); logFile.close(); std::error_code ec; if (std::filesystem::file_size(logPath, ec) == 0) { std::filesystem::remove(logPath, ec); LOG(INFO) << "No log entries found for logger " << Logger::NAME; return; } std::apply( [&directory](auto &...filter) { ( [&] { if (filter.second.empty()) { return; } using FilterType = std::decay_t; bool wrote = FilterType::write( directory / fmt::format("{}.{}.txt", Logger::NAME, FilterType::NAME), filter.second); if (!wrote) { PLOG(ERROR) << "Failed to write to log file for logger " << Logger::NAME; } }(), ...); }, filters); } namespace { constexpr std::string_view DEV_KMSG = "/dev/kmsg"; void recordBootTime() { struct sysinfo x {}; std::string logbuf; using std::chrono::seconds; if ((sysinfo(&x) == 0)) { logbuf = fmt::format("Boot completed in {:%Mm%Ss}", seconds(x.uptime)); LOG(INFO) << logbuf; WriteStringToFile(logbuf, DEV_KMSG.data()); } } bool delAllAndRecreate(const std::filesystem::path &path) { std::error_code ec; LOG(INFO) << "Deleting everything in " << path; if (fs::is_directory(path, ec)) { fs::remove_all(path, ec); if (ec) { LOG(ERROR) << fmt::format("Failed to remove directory '{}': {}", path.string(), ec.message()); return false; } } LOG(INFO) << "Recreating directory..."; if (!fs::create_directories(path, ec) && ec) { LOG(ERROR) << fmt::format("Failed to create directory '{}': {}", path.string(), ec.message()); return false; } return true; } } // namespace int main(int argc, char **argv) { std::vector threads; std::atomic_bool run; bool system_log = false; std::mutex lock; fs::path kLogDir; android::base::InitLogging(argv); umask(022); if (argc != 3) { fmt::print(stderr, "Usage: {} [log directory] [directory name]\n", argv[0]); return EXIT_FAILURE; } kLogDir = argv[1]; if (kLogDir.empty()) { fmt::print(stderr, "{}: Invalid empty string for log directory\n", argv[0]); return EXIT_FAILURE; } kLogDir /= argv[2]; if (getenv("LOGGER_MODE_SYSTEM") != nullptr) { LOG(INFO) << "Running in system log mode"; system_log = true; } LOG(INFO) << fmt::format("Logger starting with logdir '{}'...", kLogDir.string()); // Determine audit support bool has_audit = false; if (GetBoolProperty(MAKE_LOGGER_PROP("audit_filter_enabled"), true)) { if (KernelConfigType kConfig; ReadKernelConfig(kConfig) == 0) { if (kConfig["CONFIG_AUDIT"] == ConfigValue::BUILT_IN) { LOG(INFO) << "Detected CONFIG_AUDIT=y in kernel configuration"; has_audit = true; } } } if (!delAllAndRecreate(kLogDir)) { return EXIT_FAILURE; } run = true; // If this prop is true, logd logs kernel message to logcat // Don't make duplicate (Also it will race against kernel logs) if (!GetBoolProperty("ro.logd.kernel", false)) { threads.emplace_back([&] { if (has_audit) { start(kLogDir, &run); } else { start(kLogDir, &run); } }); } std::ofstream timestamp(kLogDir / "TIMESTAMP"); if (timestamp.is_open()) { timestamp << fmt::format("{:%F %T}", std::chrono::system_clock::now()); } timestamp.close(); threads.emplace_back( [&] { start(kLogDir, &run); }); if (system_log) { WaitForProperty(MAKE_LOGGER_PROP("enabled"), "false"); } else { WaitForProperty("sys.boot_completed", "1"); recordBootTime(); } LOG(INFO) << "Woke up, waiting for threads to finish"; run = false; for (auto &i : threads) { i.join(); } LOG(INFO) << "Logger stopped"; return 0; }