blob: 1933367a815820ca0edf09e8838286a03036893c [file] [log] [blame]
// Copyright 2020 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 "diagnostics/cros_healthd/executor/executor_mojo_service.h"
#include <inttypes.h>
#include <csignal>
#include <cstdint>
#include <cstdlib>
#include <utility>
#include <base/bind.h>
#include <base/check.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <base/system/sys_info.h>
#include <base/task/task_traits.h>
#include <base/task/thread_pool.h>
#include <base/time/time.h>
#include <brillo/process/process.h>
#include "diagnostics/cros_healthd/process/process_with_output.h"
#include "diagnostics/cros_healthd/utils/file_utils.h"
#include "mojo/cros_healthd_executor.mojom.h"
namespace diagnostics {
namespace {
namespace mojo_ipc = ::chromeos::cros_healthd_executor::mojom;
// Amount of time we wait for a process to respond to SIGTERM before killing it.
constexpr base::TimeDelta kTerminationTimeout = base::TimeDelta::FromSeconds(2);
// All SECCOMP policies should live in this directory.
constexpr char kSandboxDirPath[] = "/usr/share/policy/";
// SECCOMP policy for ectool pwmgetfanrpm:
constexpr char kFanSpeedSeccompPolicyPath[] =
"ectool_pwmgetfanrpm-seccomp.policy";
constexpr char kEctoolUserAndGroup[] = "healthd_ec";
constexpr char kEctoolBinary[] = "/usr/sbin/ectool";
// The ectool command used to collect fan speed in RPM.
constexpr char kGetFanRpmCommand[] = "pwmgetfanrpm";
// SECCOMP policy for memtester, relative to kSandboxDirPath.
constexpr char kMemtesterSeccompPolicyPath[] = "memtester-seccomp.policy";
constexpr char kMemtesterBinary[] = "/usr/sbin/memtester";
// All Mojo callbacks need to be ran by the Mojo task runner, so this provides a
// convenient wrapper that can be bound and ran by that specific task runner.
void RunMojoProcessResultCallback(
mojo_ipc::ProcessResult mojo_result,
base::OnceCallback<void(mojo_ipc::ProcessResultPtr)> callback) {
std::move(callback).Run(mojo_result.Clone());
}
} // namespace
ExecutorMojoService::ExecutorMojoService(
const scoped_refptr<base::SingleThreadTaskRunner> mojo_task_runner,
mojo_ipc::ExecutorRequest request)
: mojo_task_runner_(mojo_task_runner),
binding_{this /* impl */, std::move(request)} {
binding_.set_connection_error_handler(
base::BindOnce([]() { std::exit(EXIT_SUCCESS); }));
}
void ExecutorMojoService::GetFanSpeed(GetFanSpeedCallback callback) {
mojo_ipc::ProcessResult result;
const auto seccomp_policy_path =
base::FilePath(kSandboxDirPath).Append(kFanSpeedSeccompPolicyPath);
// Minijail setup for ectool.
std::vector<std::string> sandboxing_args;
sandboxing_args.push_back("-G");
sandboxing_args.push_back("-c");
sandboxing_args.push_back("cap_sys_rawio=e");
sandboxing_args.push_back("-b");
sandboxing_args.push_back("/dev/cros_ec");
std::vector<std::string> binary_args = {kGetFanRpmCommand};
base::FilePath binary_path = base::FilePath(kEctoolBinary);
base::OnceClosure closure = base::BindOnce(
&ExecutorMojoService::RunUntrackedBinary, weak_factory_.GetWeakPtr(),
seccomp_policy_path, sandboxing_args, kEctoolUserAndGroup, binary_path,
binary_args, std::move(result), std::move(callback));
base::ThreadPool::PostTask(FROM_HERE, {base::MayBlock()}, std::move(closure));
}
void ExecutorMojoService::RunMemtester(RunMemtesterCallback callback) {
mojo_ipc::ProcessResult result;
// Only allow one instance of memtester at a time. This is reasonable, because
// memtester mlocks almost the entirety of the device's memory, and a second
// memtester process wouldn't have any memory to test.
auto itr = processes_.find(kMemtesterBinary);
if (itr != processes_.end()) {
result.return_code = EXIT_FAILURE;
result.err = "Memtester process already running.";
std::move(callback).Run(result.Clone());
return;
}
int64_t available_mem = base::SysInfo::AmountOfAvailablePhysicalMemory();
// Convert from bytes to MB.
available_mem /= (1024 * 1024);
// Make sure the operating system is left with at least 200 MB.
available_mem -= 200;
if (available_mem <= 0) {
result.err = "Not enough available memory to run memtester.";
result.return_code = EXIT_FAILURE;
std::move(callback).Run(result.Clone());
return;
}
// Minijail setup for memtester.
std::vector<std::string> sandboxing_args;
sandboxing_args.push_back("-c");
sandboxing_args.push_back("cap_ipc_lock=e");
// Additional args for memtester.
std::vector<std::string> memtester_args;
// Run with all free memory, except that which we left to the operating system
// above.
memtester_args.push_back(base::StringPrintf("%" PRId64, available_mem));
// Run for one loop.
memtester_args.push_back("1");
const auto kSeccompPolicyPath =
base::FilePath(kSandboxDirPath).Append(kMemtesterSeccompPolicyPath);
// Since no user:group is specified, this will run with the default
// cros_healthd:cros_healthd user and group.
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&ExecutorMojoService::RunTrackedBinary,
weak_factory_.GetWeakPtr(), kSeccompPolicyPath,
sandboxing_args, base::nullopt,
base::FilePath(kMemtesterBinary), memtester_args,
std::move(result), std::move(callback)));
}
void ExecutorMojoService::KillMemtester() {
base::AutoLock auto_lock(lock_);
auto itr = processes_.find(kMemtesterBinary);
if (itr == processes_.end())
return;
brillo::Process* process = itr->second.get();
// If the process has ended, don't try to kill anything.
if (!process->pid())
return;
// Try to terminate the process nicely, then kill it if necessary.
if (!process->Kill(SIGTERM, kTerminationTimeout.InSeconds()))
process->Kill(SIGKILL, kTerminationTimeout.InSeconds());
}
void ExecutorMojoService::GetProcessIOContents(
const uint32_t pid, GetProcessIOContentsCallback callback) {
std::string result;
ReadAndTrimString(base::FilePath("/proc/")
.Append(base::StringPrintf("%" PRId32, pid))
.AppendASCII("io"),
&result);
std::move(callback).Run(result);
}
void ExecutorMojoService::RunUntrackedBinary(
const base::FilePath& seccomp_policy_path,
const std::vector<std::string>& sandboxing_args,
const base::Optional<std::string>& user,
const base::FilePath& binary_path,
const std::vector<std::string>& binary_args,
mojo_ipc::ProcessResult result,
base::OnceCallback<void(mojo_ipc::ProcessResultPtr)> callback) {
auto process = std::make_unique<ProcessWithOutput>();
result.return_code =
RunBinaryInternal(seccomp_policy_path, sandboxing_args, user, binary_path,
binary_args, &result, process.get());
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RunMojoProcessResultCallback,
std::move(result), std::move(callback)));
}
void ExecutorMojoService::RunTrackedBinary(
const base::FilePath& seccomp_policy_path,
const std::vector<std::string>& sandboxing_args,
const base::Optional<std::string>& user,
const base::FilePath& binary_path,
const std::vector<std::string>& binary_args,
mojo_ipc::ProcessResult result,
base::OnceCallback<void(mojo_ipc::ProcessResultPtr)> callback) {
std::string binary_path_str = binary_path.value();
DCHECK(!processes_.count(binary_path_str));
{
auto process = std::make_unique<ProcessWithOutput>();
base::AutoLock auto_lock(lock_);
processes_[binary_path_str] = std::move(process);
}
result.return_code = RunBinaryInternal(
seccomp_policy_path, sandboxing_args, user, binary_path, binary_args,
&result, processes_[binary_path_str].get());
// Get rid of the process.
base::AutoLock auto_lock(lock_);
auto itr = processes_.find(binary_path_str);
DCHECK(itr != processes_.end());
processes_.erase(itr);
mojo_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RunMojoProcessResultCallback,
std::move(result), std::move(callback)));
}
int ExecutorMojoService::RunBinaryInternal(
const base::FilePath& seccomp_policy_path,
const std::vector<std::string>& sandboxing_args,
const base::Optional<std::string>& user,
const base::FilePath& binary_path,
const std::vector<std::string>& binary_args,
mojo_ipc::ProcessResult* result,
ProcessWithOutput* process) {
DCHECK(result);
DCHECK(process);
if (!base::PathExists(seccomp_policy_path)) {
result->err = "Sandbox info is missing for this architecture.";
return EXIT_FAILURE;
}
// Sandboxing setup for the process.
if (user.has_value())
process->SandboxAs(user.value(), user.value());
process->SetSeccompFilterPolicyFile(seccomp_policy_path.MaybeAsASCII());
process->set_separate_stderr(true);
if (!process->Init(sandboxing_args)) {
result->err = "Process initialization failure.";
return EXIT_FAILURE;
}
process->AddArg(binary_path.MaybeAsASCII());
for (const auto& arg : binary_args)
process->AddArg(arg);
int exit_code = process->Run();
if (exit_code != EXIT_SUCCESS) {
process->GetError(&result->err);
return exit_code;
}
if (!process->GetOutput(&result->out)) {
result->err = "Failed to get output from process.";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
} // namespace diagnostics