blob: 5626dd8a16c9a0a959dddd8b37aaa0ddf9daacdc [file] [log] [blame]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "secagentd/message_sender.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "google/protobuf/message_lite.h"
#include "missive/client/report_queue.h"
#include "missive/client/report_queue_configuration.h"
#include "missive/client/report_queue_factory.h"
#include "missive/proto/record_constants.pb.h"
#include "missive/util/status.h"
#include "re2/re2.h"
#include "secagentd/proto/security_xdr_events.pb.h"
namespace secagentd {
namespace pb = cros_xdr::reporting;
namespace {
void SendMessageStatusCallback(
reporting::Destination destination,
std::optional<reporting::ReportQueue::EnqueueCallback> cb,
reporting::Status status) {
if (!status.ok()) {
LOG(ERROR) << destination << ", status=" << status;
}
if (cb.has_value()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(cb.value()), status));
}
}
void VlogEventForDebug(reporting::Destination destination,
google::protobuf::MessageLite* message) {
switch (destination) {
case reporting::CROS_SECURITY_AGENT: {
auto agent_event =
google::protobuf::internal::down_cast<pb::XdrAgentEvent*>(message);
VLOG(1) << "XdrAgentEvent:";
VLOG(1) << agent_event->DebugString();
break;
}
case reporting::CROS_SECURITY_PROCESS: {
auto process_event =
google::protobuf::internal::down_cast<pb::XdrProcessEvent*>(message);
VLOG(1) << "XdrProcessEvent:";
VLOG(1) << process_event->DebugString();
break;
}
default:
LOG(FATAL) << "Unknown destination: " << destination;
}
VLOG(1) << "Raw serialized message:";
// GetQuotedJSONString just escapes the given string for cleaner output.
// There's no actual JSON conversion happening here.
VLOG(1) << base::GetQuotedJSONString(message->SerializeAsString());
}
} // namespace
MessageSender::MessageSender(const base::FilePath& root_path)
: root_path_(root_path) {}
MessageSender::MessageSender() : MessageSender(base::FilePath("/")) {}
void MessageSender::UpdateDeviceTz(const base::FilePath& timezone_symlink,
bool error) {
static constexpr char kTimezoneFilesDir[] = "usr/share/zoneinfo/";
if (error) {
LOG(ERROR) << "TZ symlink watch was aborted due to a system error.";
return;
}
const base::FilePath zoneinfo_dir = root_path_.Append(kTimezoneFilesDir);
base::FilePath timezone_file;
if (!base::ReadSymbolicLink(timezone_symlink, &timezone_file)) {
LOG(ERROR) << "Failed to resolve symlink at " << timezone_symlink.value();
return;
}
base::FilePath relpath;
if (!zoneinfo_dir.AppendRelativePath(timezone_file, &relpath)) {
LOG(ERROR) << "Failed to find relative zoneinfo path of "
<< timezone_file.value();
return;
}
base::AutoLock lock(common_lock_);
common_.set_local_timezone(relpath.value());
LOG(INFO) << "Device timezone set to " << relpath.value();
}
void MessageSender::InitializeDeviceBtime() {
static constexpr char kProcStatFile[] = "proc/stat";
static constexpr char kProcStatBtimePattern[] = R"(\nbtime (\d+)\n)";
static const re2::LazyRE2 kStatBtimeRe = {kProcStatBtimePattern};
const base::FilePath proc_stat_file(kProcStatFile);
// Base doesn't have a ReadLine-ey helper. /proc/stat does scale with the
// number of CPU threads but shouldn't be too bad on a mobile/desktop.
// Around 1.5K on a 8-thread CPU. This is a one-time parse at startup.
std::string proc_stat_contents;
int64_t btime;
if ((!base::ReadFileToString(root_path_.Append(kProcStatFile),
&proc_stat_contents)) ||
(!RE2::PartialMatch(proc_stat_contents, *kStatBtimeRe, &btime))) {
LOG(ERROR) << "Failed to parse boot time from " << kProcStatFile;
return;
}
base::AutoLock lock(common_lock_);
common_.set_device_boot_time(btime);
LOG(INFO) << "Set device boot time to " << btime;
}
void MessageSender::InitializeAndWatchDeviceTz() {
static constexpr char kTimezoneSymlink[] = "var/lib/timezone/localtime";
const base::FilePath timezone_symlink = root_path_.Append(kTimezoneSymlink);
UpdateDeviceTz(base::FilePath(timezone_symlink), false);
if (!common_file_watcher_.Watch(
timezone_symlink, base::FilePathWatcher::Type::kNonRecursive,
base::BindRepeating(&MessageSender::UpdateDeviceTz,
base::Unretained(this)))) {
LOG(ERROR) << "Failed to add a file watch on " << timezone_symlink.value();
}
}
absl::Status MessageSender::InitializeQueues() {
// Array of possible destinations.
const reporting::Destination kDestinations[] = {
reporting::CROS_SECURITY_PROCESS, reporting::CROS_SECURITY_AGENT};
for (auto destination : kDestinations) {
auto report_queue_result =
reporting::ReportQueueFactory::CreateSpeculativeReportQueue(
reporting::EventType::kDevice, destination);
if (report_queue_result == nullptr) {
return absl::InternalError(
"InitializeQueues: Report queue failed to create");
}
queue_map_.insert(
std::make_pair(destination, std::move(report_queue_result)));
}
return absl::OkStatus();
}
absl::Status MessageSender::Initialize() {
// We don't care as much about common fields as we do about errors in creating
// the message queues.
InitializeDeviceBtime();
InitializeAndWatchDeviceTz();
return InitializeQueues();
}
void MessageSender::SendMessage(
reporting::Destination destination,
pb::CommonEventDataFields* mutable_common,
std::unique_ptr<google::protobuf::MessageLite> message,
std::optional<reporting::ReportQueue::EnqueueCallback> cb) {
auto it = queue_map_.find(destination);
CHECK(it != queue_map_.end());
if (mutable_common) {
base::AutoLock lock(common_lock_);
mutable_common->CopyFrom(common_);
}
if (logging::GetVlogVerbosity() > 0) {
VlogEventForDebug(destination, message.get());
}
it->second.get()->Enqueue(
std::move(message), reporting::SLOW_BATCH,
base::BindOnce(&SendMessageStatusCallback, destination, std::move(cb)));
}
} // namespace secagentd