blob: 698f2cd0aa400979d86193e9ce64c031305169a9 [file] [log] [blame]
// Copyright 2021 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.
#include "secanomalyd/daemon.h"
#include <string>
#include <vector>
#include <base/command_line.h>
#include <base/files/file_util.h>
#include <base/files/file_path.h>
#include <base/logging.h>
#include <base/strings/string_piece.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/time/time.h>
#include <brillo/process/process.h>
#include <brillo/message_loops/message_loop.h>
#include <vboot/crossystem.h>
#include "secanomalyd/metrics.h"
#include "secanomalyd/mount_entry.h"
#include "secanomalyd/mounts.h"
namespace {
constexpr base::TimeDelta kCheckInterval = base::TimeDelta::FromSeconds(30);
// Per Platform.DailyUseTime histogram this interval should ensure that enough
// users run the reporting.
constexpr base::TimeDelta kReportWXMountCountInterval =
base::TimeDelta::FromHours(2);
constexpr char kCrashReporterPath[] = "/sbin/crash_reporter";
constexpr char kSecurityAnomalyFlag[] = "--security_anomaly";
bool ShouldReport(bool report_in_dev_mode) {
// Reporting should only happen when booted in Verified mode and not running
// a developer image, unless explicitly instructed otherwise.
return ::VbGetSystemPropertyInt("cros_debug") == 0 || report_in_dev_mode;
}
bool ReportMount(const MountEntry& e, bool report_in_dev_mode) {
if (!ShouldReport(report_in_dev_mode)) {
VLOG(1) << "Not in Verified mode, not reporting " << e.dest();
return true;
}
VLOG(1) << "secanomalyd invoking crash_reporter";
brillo::ProcessImpl crash_reporter;
crash_reporter.AddArg(kCrashReporterPath);
crash_reporter.AddArg(kSecurityAnomalyFlag);
crash_reporter.RedirectUsingPipe(STDIN_FILENO, true);
CHECK(crash_reporter.Start());
int stdin_fd = crash_reporter.GetPipe(STDIN_FILENO);
CHECK(base::WriteFileDescriptor(stdin_fd, e.src().value()));
std::string newline = "\n";
CHECK(base::WriteFileDescriptor(stdin_fd, newline));
CHECK(base::WriteFileDescriptor(stdin_fd, e.dest().value()));
CHECK_GE(IGNORE_EINTR(close(stdin_fd)), 0);
CHECK_EQ(0, crash_reporter.Wait());
return true;
}
} // namespace
int Daemon::OnEventLoopStarted() {
CheckWXMounts();
ReportWXMountCount();
return 0;
}
void Daemon::CheckWXMounts() {
VLOG(1) << "Checking for W+X mounts";
DoWXMountCheck();
brillo::MessageLoop::current()->PostDelayedTask(
FROM_HERE, base::BindOnce(&Daemon::CheckWXMounts, base::Unretained(this)),
kCheckInterval);
}
void Daemon::ReportWXMountCount() {
VLOG(1) << "Reporting W+X mount count";
DoWXMountCountReporting();
brillo::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&Daemon::ReportWXMountCount, base::Unretained(this)),
kReportWXMountCountInterval);
}
void Daemon::DoWXMountCheck() {
MaybeMountEntries mount_entries = ReadMounts();
if (!mount_entries) {
LOG(ERROR) << "Failed to read mounts";
return;
}
for (const auto& e : mount_entries.value()) {
if (e.IsWX()) {
// Have we seen the mount yet?
if (wx_mounts_.count(e.dest()) == 0) {
if (e.IsUsbDriveOrArchive()) {
// Figure out what to log in this case.
// We could log the fact that the mount exists without logging
// |src| or |dest|.
continue;
}
if (e.IsNamespaceBindMount()) {
// Namespace mounts happen when a namespace file in /proc/<pid>/ns/
// gets bind-mounted somewhere else. These mounts can be W+X but are
// not concerning since they consist of a single file and these files
// cannot be executed.
VLOG(1) << "Not recording W+X mount at '" << e.dest() << "', type "
<< e.type();
continue;
}
// We haven't seen the mount, and it's not a type we want to skip, so
// save it.
wx_mounts_[e.dest()] = e;
VLOG(1) << "Found W+X mount at '" << e.dest() << "', type " << e.type();
VLOG(1) << "|wx_mounts_.size()| = " << wx_mounts_.size();
// Report metrics on the mount, if not running in dev mode.
if (ShouldReport(dev_)) {
// Report /usr/local mounts separately because those can indicate
// systems where |cros_debug == 0| but the system is still a dev
// system.
SecurityAnomaly mount_anomaly =
e.IsDestInUsrLocal()
? SecurityAnomaly::kMount_InitNs_WxInUsrLocal
: SecurityAnomaly::kMount_InitNs_WxNotInUsrLocal;
if (!SendSecurityAnomalyToUMA(mount_anomaly)) {
LOG(WARNING) << "Could not upload metrics";
}
}
// And report the actual anomalous mount, when required to.
if (generate_reports_) {
ReportMount(e, dev_);
}
}
}
}
}
void Daemon::DoWXMountCountReporting() {
if (ShouldReport(dev_)) {
if (!SendWXMountCountToUMA(wx_mounts_.size())) {
LOG(WARNING) << "Could not upload W+X mount count";
}
}
}