blob: 6db9fad5cda15b8f00eca64be2b9e6f801621c30 [file] [log] [blame]
// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modemfwd/modem_helper.h"
#include <utility>
#include <base/containers/contains.h>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos/switches/modemfwd_switches.h>
#include "modemfwd/logging.h"
#include "modemfwd/modem_sandbox.h"
#include "modemfwd/upstart_job_controller.h"
namespace modemfwd {
namespace {
constexpr char kUpstartServiceName[] = "com.ubuntu.Upstart";
constexpr char kHermesJobPath[] = "/com/ubuntu/Upstart/jobs/hermes";
constexpr char kModemHelperJobPath[] =
"/com/ubuntu/Upstart/jobs/modemfwd_2dhelpers";
bool RunHelperProcessWithLogs(const HelperInfo& helper_info,
const std::vector<std::string>& arguments) {
int child_stdout = -1, child_stderr = -1;
std::vector<std::string> formatted_args;
bool should_remove_capabilities = true;
formatted_args.push_back(helper_info.executable_path.value());
for (const std::string& argument : arguments)
formatted_args.push_back("--" + argument);
for (const std::string& extra_argument : helper_info.extra_arguments)
formatted_args.push_back(extra_argument);
// Determine where this helper's seccomp policy would be. Expected location:
// /usr/share/policy/{modem_id}-helper-seccomp.policy
const base::FilePath helper_seccomp_policy_file(base::StringPrintf(
"%s/%s-seccomp.policy", kSeccompPolicyDirectory,
helper_info.executable_path.BaseName().value().c_str()));
// Allow cap_net_admin to persist if the helper requires it
if (helper_info.net_admin_required)
should_remove_capabilities = false;
int exit_code = RunProcessInSandbox(
formatted_args, helper_seccomp_policy_file, should_remove_capabilities,
&child_stdout, &child_stderr);
base::Time::Exploded time;
base::Time::Now().LocalExplode(&time);
const std::string output_log_file = base::StringPrintf(
"%s/helper_log.%4u%02u%02u-%02u%02u%02u%03u", kModemfwdLogDirectory,
time.year, time.month, time.day_of_month, time.hour, time.minute,
time.second, time.millisecond);
base::ScopedFD scoped_stdout(child_stdout);
base::ScopedFD scoped_stderr(child_stderr);
if (scoped_stdout.is_valid()) {
base::File stdout_file = base::File(std::move(scoped_stdout));
base::File dest_stdout_file =
base::File(base::FilePath(output_log_file),
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
base::CopyFileContents(stdout_file, dest_stdout_file);
}
if (exit_code != 0) {
LOG(ERROR) << "Failed to perform \"" << base::JoinString(arguments, " ")
<< "\" on the modem with retcode " << exit_code;
return false;
}
return true;
}
bool RunHelperProcess(const HelperInfo& helper_info,
const std::vector<std::string>& arguments,
std::string* output) {
int child_stdout = -1, child_stderr = -1;
std::vector<std::string> formatted_args;
bool should_remove_capabilities = true;
formatted_args.push_back(helper_info.executable_path.value());
for (const std::string& argument : arguments)
formatted_args.push_back("--" + argument);
for (const std::string& extra_argument : helper_info.extra_arguments)
formatted_args.push_back(extra_argument);
// Determine where this helper's seccomp policy would be. Expected location:
// /usr/share/policy/{modem_id}-helper-seccomp.policy
const base::FilePath helper_seccomp_policy_file(base::StringPrintf(
"%s/%s-seccomp.policy", kSeccompPolicyDirectory,
helper_info.executable_path.BaseName().value().c_str()));
// Allow cap_net_admin to persist if the helper requires it
if (helper_info.net_admin_required)
should_remove_capabilities = false;
int exit_code = RunProcessInSandbox(
formatted_args, helper_seccomp_policy_file, should_remove_capabilities,
&child_stdout, &child_stderr);
base::ScopedFD scoped_stdout(child_stdout);
base::ScopedFD scoped_stderr(child_stderr);
if (output && scoped_stdout.is_valid()) {
base::File output_base_file = base::File(std::move(scoped_stdout));
DCHECK(output_base_file.IsValid());
const int kBufSize = 1024;
char buf[kBufSize];
int bytes_read = output_base_file.ReadAtCurrentPos(buf, kBufSize);
if (bytes_read != -1)
output->assign(buf, bytes_read);
}
if (exit_code != 0) {
LOG(ERROR) << "Failed to perform \"" << base::JoinString(arguments, " ")
<< "\" on the modem with retcode " << exit_code;
return false;
}
return true;
}
// Ensures we reboot the modem to prevent us from leaving it in a bad state.
class FlashMode {
public:
explicit FlashMode(const HelperInfo& helper_info)
: helper_info_(helper_info) {}
~FlashMode() { RunHelperProcess(helper_info_, {kReboot}, nullptr); }
private:
FlashMode(const FlashMode&) = delete;
FlashMode& operator=(const FlashMode&) = delete;
HelperInfo helper_info_;
};
} // namespace
class ModemHelperImpl : public ModemHelper {
public:
explicit ModemHelperImpl(const HelperInfo& helper_info,
scoped_refptr<dbus::Bus> bus)
: helper_info_(helper_info), bus_(bus) {}
ModemHelperImpl(const ModemHelperImpl&) = delete;
ModemHelperImpl& operator=(const ModemHelperImpl&) = delete;
~ModemHelperImpl() override = default;
bool GetFirmwareInfo(FirmwareInfo* out_info,
const std::string& firmware_revision) override {
CHECK(out_info);
std::string helper_output;
if (!RunHelperProcess(helper_info_,
{kGetFirmwareInfo,
base::StringPrintf("%s=%s", kShillFirmwareRevision,
firmware_revision.c_str())},
&helper_output)) {
return false;
}
base::StringPairs parsed_versions;
bool result = base::SplitStringIntoKeyValuePairs(helper_output, ':', '\n',
&parsed_versions);
if (parsed_versions.size() == 0) {
LOG(WARNING) << "Modem helper returned malformed firmware version info";
return false;
}
if (!result) {
LOG(WARNING) << "Modem helper returned malformed firmware version info,"
<< " part of version info failed to parse.";
}
for (const auto& pair : parsed_versions) {
if (pair.first == kFwMain)
out_info->main_version = pair.second;
else if (pair.first == kFwCarrier)
out_info->carrier_version = pair.second;
else if (pair.first == kFwCarrierUuid)
out_info->carrier_uuid = pair.second;
else if (pair.first == kFwOem)
out_info->oem_version = pair.second;
else if (pair.first == "")
continue;
else
out_info->assoc_versions.insert(pair);
}
return true;
}
// modemfwd::ModemHelper overrides.
bool FlashFirmwares(const std::vector<FirmwareConfig>& configs) override {
FlashMode flash_mode(helper_info_);
if (!configs.size())
return false;
UpstartJobController hermes(kUpstartServiceName, kHermesJobPath, bus_);
if (hermes.IsRunning())
hermes.Stop(); // Job starts automatically upon exiting scope
std::vector<std::string> firmwares;
std::vector<std::string> upstart_in_env;
std::vector<std::string> versions;
for (const auto& config : configs) {
firmwares.push_back(base::StringPrintf("%s:%s", config.fw_type.c_str(),
config.path.value().c_str()));
upstart_in_env.push_back(base::StringPrintf(
"%s=%s", config.fw_type.c_str(), config.path.value().c_str()));
versions.push_back(base::StringPrintf("%s:%s", config.fw_type.c_str(),
config.version.c_str()));
}
// If installed, modemfwd-helpers.conf may be used to perform actions with
// the fw that only root can perform. upstart_in_env must be checked by
// modemfwd-helpers.conf.
UpstartJobController modemfwd_helpers(kUpstartServiceName,
kModemHelperJobPath, bus_);
if (modemfwd_helpers.IsInstalled() &&
!modemfwd_helpers.Start(upstart_in_env)) {
LOG(ERROR) << "Failed to start modemfwd-helpers";
return false;
}
return RunHelperProcessWithLogs(
helper_info_,
{base::StringPrintf("%s=%s", kFlashFirmware,
base::JoinString(firmwares, ",").c_str()),
base::StringPrintf("%s=%s", kFwVersion,
base::JoinString(versions, ",").c_str())});
}
bool FlashModeCheck() override {
std::string output;
if (!RunHelperProcess(helper_info_, {kFlashModeCheck}, &output))
return false;
return base::TrimWhitespaceASCII(output, base::TRIM_ALL) == "true";
}
bool Reboot() override {
return RunHelperProcess(helper_info_, {kReboot}, nullptr);
}
bool ClearAttachAPN(const std::string& carrier_uuid) override {
return RunHelperProcess(
helper_info_,
{base::StringPrintf("%s=%s", kClearAttachAPN, carrier_uuid.c_str())},
nullptr);
}
std::optional<HeartbeatConfig> GetHeartbeatConfig() override {
std::string output;
if (!RunHelperProcess(helper_info_, {kGetHeartbeatConfig}, &output))
return std::nullopt;
base::StringPairs parsed_config;
bool result =
base::SplitStringIntoKeyValuePairs(output, ':', '\n', &parsed_config);
if (!result) {
LOG(WARNING) << "Modem helper returned malformed heartbeat config";
return std::nullopt;
}
std::optional<int> max_failures;
std::optional<int> interval_sec;
for (const auto& pair : parsed_config) {
if (pair.first == kHeartbeatMaxFailures) {
int value;
if (!base::StringToInt(pair.second, &value))
return std::nullopt;
max_failures = value;
}
if (pair.first == kHeartbeatInterval) {
int value;
if (!base::StringToInt(pair.second, &value))
return std::nullopt;
interval_sec = value;
}
}
if (!max_failures.has_value() || !interval_sec.has_value()) {
LOG(WARNING) << "Modem helper returned incomplete heartbeat config";
return std::nullopt;
}
return HeartbeatConfig{*max_failures, base::Seconds(*interval_sec)};
}
private:
HelperInfo helper_info_;
scoped_refptr<dbus::Bus> bus_;
};
std::unique_ptr<ModemHelper> CreateModemHelper(const HelperInfo& helper_info,
scoped_refptr<dbus::Bus> bus) {
return std::make_unique<ModemHelperImpl>(helper_info, bus);
}
} // namespace modemfwd