1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
|
// 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/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 "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) {
std::optional<int64_t> size =
base::GetFileSize(GetUpdateLocationFilePath());
if (!size.has_value()) {
// File doesn't exist or error - can't determine availability status.
return false;
}
status->update_available = size.value() > 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 enrollment check is required, enterprise enrollment
// might still be pending, in which case TPM firmware updates are
// disallowed until enrollment state determination determines that the
// device is not remotely managed or it does get enrolled and the admin
// allows TPM firmware updates.
if (policy::AutoEnrollmentTypeChecker::IsEnabled()) {
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
|