// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/tpm_firmware_update.h"
#include <memory>
#include <utility>
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/enrollment/auto_enrollment_type_checker.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chromeos/ash/components/install_attributes/install_attributes.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
namespace ash {
namespace tpm_firmware_update {
namespace {
// Decodes a |settings| dictionary into a set of allowed update modes.
std::set<Mode> GetModesFromSetting(const base::Value* settings) {
std::set<Mode> modes;
if (!settings)
return modes;
const base::Value::Dict& settings_dict = settings->GetDict();
std::optional<bool> allow_powerwash =
settings_dict.FindBool(kSettingsKeyAllowPowerwash);
if (allow_powerwash && *allow_powerwash) {
modes.insert(Mode::kPowerwash);
}
std::optional<bool> allow_preserve_device_state =
settings_dict.FindBool(kSettingsKeyAllowPreserveDeviceState);
if (allow_preserve_device_state && *allow_preserve_device_state) {
modes.insert(Mode::kPreserveDeviceState);
}
return modes;
}
} // namespace
const char kSettingsKeyAllowPowerwash[] = "allow-user-initiated-powerwash";
const char kSettingsKeyAllowPreserveDeviceState[] =
"allow-user-initiated-preserve-device-state";
const char kSettingsKeyAutoUpdateMode[] = "auto-update-mode";
base::Value DecodeSettingsProto(
const enterprise_management::TPMFirmwareUpdateSettingsProto& settings) {
base::Value::Dict result;
if (settings.has_allow_user_initiated_powerwash()) {
result.Set(kSettingsKeyAllowPowerwash,
settings.allow_user_initiated_powerwash());
}
if (settings.has_allow_user_initiated_preserve_device_state()) {
result.Set(kSettingsKeyAllowPreserveDeviceState,
settings.allow_user_initiated_preserve_device_state());
}
if (settings.has_auto_update_mode()) {
result.Set(kSettingsKeyAutoUpdateMode, settings.auto_update_mode());
}
return base::Value(std::move(result));
}
// AvailabilityChecker tracks TPM firmware update availability information
// exposed by the system via the /run/tpm_firmware_update file. There are three
// states:
// 1. The file isn't present - availability check is still pending.
// 2. The file is present, but empty - no update available.
// 3. The file is present, non-empty - update binary path is in the file.
//
// AvailabilityChecker employs a FilePathWatcher to watch the file and hides
// away all the gory threading details.
class AvailabilityChecker {
public:
struct Status {
bool update_available = false;
bool srk_vulnerable_roca = false;
};
using ResponseCallback = base::OnceCallback<void(const Status&)>;
AvailabilityChecker(const AvailabilityChecker&) = delete;
AvailabilityChecker& operator=(const AvailabilityChecker&) = delete;
~AvailabilityChecker() { Cancel(); }
static void Start(ResponseCallback callback, base::TimeDelta timeout) {
// Schedule a task to run when the timeout expires. The task also owns
// |checker| and thus takes care of eventual deletion.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&AvailabilityChecker::OnTimeout,
std::make_unique<AvailabilityChecker>(std::move(callback))),
timeout);
}
// Don't call this directly, but use Start().
explicit AvailabilityChecker(ResponseCallback callback)
: callback_(std::move(callback)),
background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE})),
watcher_(new base::FilePathWatcher()) {
auto watch_callback =
base::BindRepeating(&AvailabilityChecker::OnFilePathChanged,
base::SequencedTaskRunner::GetCurrentDefault(),
weak_ptr_factory_.GetWeakPtr());
background_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&AvailabilityChecker::StartOnBackgroundThread,
watcher_.get(), watch_callback));
}
private:
static base::FilePath GetUpdateLocationFilePath() {
base::FilePath update_location_file;
CHECK(base::PathService::Get(
chrome::FILE_CHROME_OS_TPM_FIRMWARE_UPDATE_LOCATION,
&update_location_file));
return update_location_file;
}
static bool CheckAvailabilityStatus(Status* status) {
int64_t size;
if (!base::GetFileSize(GetUpdateLocationFilePath(), &size)) {
// File doesn't exist or error - can't determine availability status.
return false;
}
status->update_available = size > 0;
base::FilePath srk_vulnerable_roca_file;
CHECK(base::PathService::Get(
chrome::FILE_CHROME_OS_TPM_FIRMWARE_UPDATE_SRK_VULNERABLE_ROCA,
&srk_vulnerable_roca_file));
status->srk_vulnerable_roca = base::PathExists(srk_vulnerable_roca_file);
return true;
}
static void StartOnBackgroundThread(
base::FilePathWatcher* watcher,
base::FilePathWatcher::Callback watch_callback) {
watcher->Watch(GetUpdateLocationFilePath(),
base::FilePathWatcher::Type::kNonRecursive, watch_callback);
watch_callback.Run(base::FilePath(), false /* error */);
}
static void OnFilePathChanged(
scoped_refptr<base::SequencedTaskRunner> origin_task_runner,
base::WeakPtr<AvailabilityChecker> checker,
const base::FilePath& target,
bool error) {
Status status;
if (CheckAvailabilityStatus(&status) || error) {
origin_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&AvailabilityChecker::Resolve, checker, status));
}
}
void Resolve(const Status& status) {
Cancel();
if (callback_) {
std::move(callback_).Run(status);
}
}
void Cancel() {
// Neutralize further callbacks from |watcher_| or due to timeout.
weak_ptr_factory_.InvalidateWeakPtrs();
background_task_runner_->DeleteSoon(FROM_HERE, std::move(watcher_));
}
void OnTimeout() {
// If |callback_| hasn't been triggered when the timeout task fires, perform
// a last check and wire the result into a |callback_| execution to make
// sure a result is delivered in all cases. Note that |OnTimeout()| gets run
// via a callback that owns |this|, so the object will be destructed after
// this function terminates. Thus, the final check needs to run independent
// of |this| and takes |callback_| ownership.
if (callback_) {
background_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce([]() {
Status status;
CheckAvailabilityStatus(&status);
return status;
}),
std::move(callback_));
}
}
ResponseCallback callback_;
scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
std::unique_ptr<base::FilePathWatcher> watcher_;
base::WeakPtrFactory<AvailabilityChecker> weak_ptr_factory_{this};
};
void GetAvailableUpdateModes(
base::OnceCallback<void(const std::set<Mode>&)> completion,
base::TimeDelta timeout) {
if (!base::FeatureList::IsEnabled(features::kTPMFirmwareUpdate)) {
std::move(completion).Run(std::set<Mode>());
return;
}
std::set<Mode> modes;
if (g_browser_process->platform_part()
->browser_policy_connector_ash()
->IsDeviceEnterpriseManaged()) {
// Split |completion| in two. This is necessary because of the
// PrepareTrustedValues API, which for some return values invokes the
// callback passed to it, and for others requires the code here to do so.
auto split_completion = base::SplitOnceCallback(std::move(completion));
// For enterprise-managed devices, always honor the device setting.
CrosSettings* const cros_settings = CrosSettings::Get();
switch (cros_settings->PrepareTrustedValues(
base::BindOnce(&GetAvailableUpdateModes,
std::move(split_completion.first), timeout))) {
case CrosSettingsProvider::TEMPORARILY_UNTRUSTED:
// Retry happens via the callback registered above.
return;
case CrosSettingsProvider::PERMANENTLY_UNTRUSTED:
// No device settings? Default to disallow.
std::move(split_completion.second).Run(std::set<Mode>());
return;
case CrosSettingsProvider::TRUSTED:
// Setting is present and trusted so respect its value.
modes = GetModesFromSetting(
cros_settings->GetPref(kTPMFirmwareUpdateSettings));
// Reset |completion| here so we can invoke it further down.
completion = std::move(split_completion.second);
break;
}
} else {
// Consumer device or still in OOBE.
if (!InstallAttributes::Get()->IsDeviceLocked()) {
// Device in OOBE. If FRE is required, enterprise enrollment might still
// be pending, in which case TPM firmware updates are disallowed until
// FRE determines that the device is not remotely managed or it does get
// enrolled and the admin allows TPM firmware updates.
const auto requirement =
policy::AutoEnrollmentTypeChecker::GetFRERequirementAccordingToVPD(
system::StatisticsProvider::GetInstance());
if (requirement == policy::AutoEnrollmentTypeChecker::FRERequirement::
kExplicitlyRequired) {
std::move(completion).Run(std::set<Mode>());
return;
}
}
// All modes are available for consumer devices.
modes.insert(Mode::kPowerwash);
modes.insert(Mode::kPreserveDeviceState);
}
// No need to check for availability if no update modes are allowed.
if (modes.empty()) {
std::move(completion).Run(std::set<Mode>());
return;
}
// Some TPM firmware update modes are allowed. Last thing to check is whether
// there actually is a pending update.
AvailabilityChecker::Start(
base::BindOnce(
[](std::set<Mode> modes,
base::OnceCallback<void(const std::set<Mode>&)> callback,
const AvailabilityChecker::Status& status) {
DCHECK_LT(0U, modes.size());
DCHECK_EQ(0U, modes.count(Mode::kCleanup));
if (status.update_available) {
std::move(callback).Run(modes);
return;
}
// If there is no update, but the SRK is vulnerable, allow cleanup
// to take place. Note that at least one allowed actual mode is
// allowed, which is taken to imply cleanup is also allowed.
if (status.srk_vulnerable_roca) {
std::move(callback).Run(std::set<Mode>({Mode::kCleanup}));
return;
}
std::move(callback).Run(std::set<Mode>());
},
std::move(modes), std::move(completion)),
timeout);
}
void UpdateAvailable(base::OnceCallback<void(bool)> completion,
base::TimeDelta timeout) {
// Verify if we have updates pending.
AvailabilityChecker::Start(
base::BindOnce(
[](base::OnceCallback<void(bool)> completion,
const AvailabilityChecker::Status& status) {
std::move(completion).Run(status.update_available);
},
std::move(completion)),
timeout);
}
} // namespace tpm_firmware_update
} // namespace ash