Files
2025-05-11 10:04:29 +09:00

416 lines
11 KiB
C++

/*
* 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 <algorithm>
#include <android-base/file.h>
#include <android-base/properties.h>
#include <cerrno>
#include <chrono>
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <fmt/chrono.h>
#include <fmt/core.h>
#include <fmt/format.h>
#include <iomanip>
#include <limits>
#include <set>
#include <string_view>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <system_error>
#include <type_traits>
#include <unistd.h>
#include <array>
#include <atomic>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <memory>
#include <mutex>
#include <regex>
#include <sstream>
#include <string>
#include <sys/wait.h>
#include <thread>
#include <utility>
#include <vector>
#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<Data, void (*)(Data *)>;
static HANDLE open() {
HANDLE empty{nullptr, +[](Data * /*file*/) {}};
std::array<int, 2> out_fds = {};
std::array<int, 2> 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<uint8_t>::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<char, 64> 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 <size_t size>
static const char *fgets(std::array<char, size> &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<FILE, int (*)(FILE *)>;
static HANDLE open() { return {fopen(FILEC.data(), "r"), &fclose}; }
template <size_t size>
static const char *fgets(std::array<char, size> &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<std::string> &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<std::string> &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 <typename Logger, typename... Filters>
void start(const std::filesystem::path &directory, std::atomic_bool *run) {
std::array<char, 512> 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<std::pair<Filters, std::set<std::string>>...> 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<decltype(filter.first)>;
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<std::thread> 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<Dmesg, FilterAvc, FilterAvcGen>(kLogDir, &run);
} else {
start<Dmesg>(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<Logcat, FilterAvc, FilterAvcGen>(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;
}