| // 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 |