blob: 30382384a0ef95823859da02465c6057a207c27f [file] [log] [blame]
// Copyright 2017 The Chromium 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 "chrome/browser/chromeos/tpm_firmware_update.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#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/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/sequenced_task_runner.h"
#include "base/task/post_task.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/login/enrollment/auto_enrollment_controller.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
namespace chromeos {
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* const allow_powerwash = settings->FindKeyOfType(
kSettingsKeyAllowPowerwash, base::Value::Type::BOOLEAN);
if (allow_powerwash && allow_powerwash->GetBool()) {
modes.insert(Mode::kPowerwash);
}
const base::Value* const allow_preserve_device_state =
settings->FindKeyOfType(kSettingsKeyAllowPreserveDeviceState,
base::Value::Type::BOOLEAN);
if (allow_preserve_device_state && allow_preserve_device_state->GetBool()) {
modes.insert(Mode::kPreserveDeviceState);
}
return modes;
}
} // namespace
const char kSettingsKeyAllowPowerwash[] = "allow-user-initiated-powerwash";
const char kSettingsKeyAllowPreserveDeviceState[] =
"allow-user-initiated-preserve-device-state";
std::unique_ptr<base::DictionaryValue> DecodeSettingsProto(
const enterprise_management::TPMFirmwareUpdateSettingsProto& settings) {
std::unique_ptr<base::DictionaryValue> result =
std::make_unique<base::DictionaryValue>();
if (settings.has_allow_user_initiated_powerwash()) {
result->SetKey(kSettingsKeyAllowPowerwash,
base::Value(settings.allow_user_initiated_powerwash()));
}
if (settings.has_allow_user_initiated_preserve_device_state()) {
result->SetKey(
kSettingsKeyAllowPreserveDeviceState,
base::Value(settings.allow_user_initiated_preserve_device_state()));
}
return 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() { 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::SequencedTaskRunnerHandle::Get()->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::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE})),
watcher_(new base::FilePathWatcher()),
weak_ptr_factory_(this) {
auto watch_callback = base::BindRepeating(
&AvailabilityChecker::OnFilePathChanged,
base::SequencedTaskRunnerHandle::Get(), 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(), false /* recursive */,
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_) {
base::PostTaskAndReplyWithResult(background_task_runner_.get(), 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_;
DISALLOW_COPY_AND_ASSIGN(AvailabilityChecker);
};
void GetAvailableUpdateModes(
base::OnceCallback<void(const std::set<Mode>&)> completion,
base::TimeDelta timeout) {
// Wrap |completion| in a RepeatingCallback. This is necessary to cater to the
// somewhat awkward PrepareTrustedValues interface, which for some return
// values invokes the callback passed to it, and for others requires the code
// here to do so.
base::RepeatingCallback<void(const std::set<Mode>&)> callback(
base::AdaptCallbackForRepeating(std::move(completion)));
if (!base::FeatureList::IsEnabled(features::kTPMFirmwareUpdate)) {
callback.Run(std::set<Mode>());
return;
}
std::set<Mode> modes;
if (g_browser_process->platform_part()
->browser_policy_connector_chromeos()
->IsEnterpriseManaged()) {
// For enterprise-managed devices, always honor the device setting.
CrosSettings* const cros_settings = CrosSettings::Get();
switch (cros_settings->PrepareTrustedValues(
base::BindRepeating(&GetAvailableUpdateModes, callback, timeout))) {
case CrosSettingsProvider::TEMPORARILY_UNTRUSTED:
// Retry happens via the callback registered above.
return;
case CrosSettingsProvider::PERMANENTLY_UNTRUSTED:
// No device settings? Default to disallow.
callback.Run(std::set<Mode>());
return;
case CrosSettingsProvider::TRUSTED:
// Setting is present and trusted so respect its value.
modes = GetModesFromSetting(
cros_settings->GetPref(kTPMFirmwareUpdateSettings));
break;
}
} else {
// Consumer device or still 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 AutoEnrollmentController::FRERequirement requirement =
AutoEnrollmentController::GetFRERequirement();
if (requirement ==
AutoEnrollmentController::FRERequirement::kExplicitlyRequired) {
callback.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()) {
callback.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(callback)),
timeout);
}
} // namespace tpm_firmware_update
} // namespace chromeos